Sunday, July 9, 2017

RC5 Decoder with PIC16F887 microcontroller and CCS C compiler


RC-5 decoder with PIC16F887 protoboard circuit
This article shows how to decode IR remote controls which use Philips RC-5 protocol using PIC16F887 microcontroller and CCS PIC C compiler.
The RC5 has 14 bits per 1 code transmission, the 14 bits can be divided into 4 parts:
The first 2 bits are start bits and they are always logic 1.
The third bit called toggle bit, it can be logic 1 or logic 0.
The next 5 bits are address bits, each device type has its address number for example TV address number is 0, CD player address = 20 ............
And the last 6 bits are command bits, each button has its command number.
For the same device for example TV all the remote control buttons has the same address but each button has its command.
The toggle bit changes whenever a button is pressed.
The RC5 protocol uses Manchester coding, a logic 0 is represented by a logic high in the first half and a logic low in the second half, whereas a logic 0 is represented by a logic low in the first half and a logic high in the second half. The length of each half is 889µs which means the length of 1 bit is 1778µs.
Hardware required:
  • PIC16F887 microcontroller
  • RC5 IR remote control
  • 16x2 LCD screen
  • IR receiver
  • 47µF capacitor
  • 0.1µF ceramic capacitor (optional)
  • 10K ohm variable resistor
  • 10K ohm resistor
  • 5V Power source
  • Protoboard
  • Jumper wires
RC5 Decoder with PIC16F887 circuit:
RC-5 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.
RC5 Decoder with PIC16F887 CCS C code:
Before writing the C code of the decoder, I drew a simple state machine of the RC5 protocol which helped me a lot in the code. The state machine is shown below.
I made the code as simple and small as I can.
RC5 state machine for decoding
Where:
SP : Short Pulse (About 889µs)
LP : Long Pulse (About 1778µs)
SS: Short Space (About 889µs)
LS : Long Space (About 1778µs)
The resolution of the code is good as we're working with interrupts.
Rest of code description is below.
////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                            //
//   RC5 IR decoder with PIC16F887 CCS C code.                                                //
//   The message of the RC5 protocol is 14-bit long, 2 start bits (always 1), toggle bit,     //
//   address (5 bits) and command (6 bits).                                                   //
//   The length of 1 bit is 1778µs which can be divided into two parts of 889µs. A logic 1    //
//   is represented by 889µs low and 889µs high. A logic zero is represented by 889µs high    //
//   and 889µs low.                                                                           //
//   The RC5 protocol has 4 states: start1, mid1, start0 and mid0.                            //
//   The IR receiver output is logic high at idle state and when it receives a                //
//   burst it changes the output to logic low.                                                //
//   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.                                //
//   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 (short_   //
//   time, med_time and long_time) have to be changed.                                        //
//   After every interrupt the edge of the external interrupt is changed.                     //
//   Also, Timer1 interrupt is used for time out (very long pulse or very long space). This   //
//   interrupt resets the decoding process.                                                   //
//   The decoding results are displayed on 1602 LCD screen connected to PORTD.                //
//   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

#define short_time      700                      // Used as a minimum time for short pulse or short space (in µs)
#define   med_time     1200                      // Used as a maximum time for short pulse or short space (in µs)
#define  long_time     2000                      // Used as a maximum time for long pulse or long space (in µs)

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

short rc5_ok = 0, toggle_bit;
unsigned int8 rc5_state = 0, j, address, command;
unsigned int16 rc5_code;
#INT_EXT                                         // External interrupt
void ext_isr(void){
  unsigned int16 time;
  if(rc5_state != 0){
    time = get_timer1();                         // Store Timer1 value
    set_timer1(0);                               // Reset Timer1
  }
  switch(rc5_state){
   case 0 :                                      // Start receiving IR data (initially we're at the beginning of mid1)
    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
    rc5_state = 1;                               // Next state: end of mid1
    j = 0;
    ext_int_edge( L_TO_H );                      // Toggle external interrupt edge
    return;
   case 1 :                                        // End of mid1 ==> check if we're at the beginning of start1 or mid0
    if((time > long_time) || (time < short_time)){         // Invalid interval ==> stop decoding and reset
      rc5_state = 0;                             // Reset decoding process
      setup_timer_1(T1_DISABLED);
      ext_int_edge( H_TO_L );                    // Toggle external interrupt edge
      return;
    }
    bit_set(rc5_code, 13 - j);
    j++;
    if(j > 13){                                  // If all bits are received
      rc5_ok = 1;                                // Decoding process is OK
      disable_interrupts(INT_EXT);               // Disable the external interrupt
      return;
    }
      if(time > med_time){                       // We're at the beginning of mid0
        rc5_state = 2;                           // Next state: end of mid0
        if(j == 13){                             // If we're at the LSB bit
          rc5_ok = 1;                            // Decoding process is OK
          bit_clear(rc5_code, 0);                // Clear the LSB bit
          disable_interrupts(INT_EXT);           // Disable the external interrupt
          return;
        }
      }
      else                                       // We're at the beginning of start1
        rc5_state = 3;                           // Next state: end of start1
      ext_int_edge( H_TO_L );                    // Toggle external interrupt edge
      return;
   case 2 :                                        // End of mid0 ==> check if we're at the beginning of start0 or mid1
    if((time > long_time) || (time < short_time)){
      rc5_state = 0;                             // Reset decoding process
      setup_timer_1( T1_DISABLED);               // Disable Timer1 module
      return;
    }
    bit_clear(rc5_code, 13 - j);
    j++;
    if(time > med_time)                          // We're at the beginning of mid1
      rc5_state = 1;                             // Next state: end of mid1
    else                                         // We're at the beginning of start0
      rc5_state = 4;                             // Next state: end of start0
    ext_int_edge( L_TO_H );                      // Toggle external interrupt edge
    return;
   case 3 :                                        // End of start1 ==> check if we're at the beginning of mid1
    if((time > med_time) || (time < short_time)){           // Time interval invalid ==> stop decoding
      setup_timer_1(T1_DISABLED);                // Disable Timer1 module
      rc5_state = 0;
      return;
    }
    else                                         // We're at the beginning of mid1
      rc5_state = 1;                             // Next state: end of mid1
    ext_int_edge( L_TO_H );
    return;
   case 4 :                                        // End of start0 ==> check if we're at the beginning of mid0
    if((time > med_time) || (time < short_time)){           // Time interval invalid ==> stop decoding
      setup_timer_1(T1_DISABLED);                // Disable Timer1 module
      rc5_state = 0;                             // Reset decoding process
      ext_int_edge( H_TO_L );                    // Toggle external interrupt edge
      return;
    }
    else                                         // We're at the beginning of mid0
      rc5_state = 2;                             // Next state: end of mid0
    ext_int_edge( H_TO_L );                      // Toggle external interrupt edge
    if(j == 13){                                 // If we're at the LSB bit
      rc5_ok = 1;                                // Decoding process is OK
      bit_clear(rc5_code, 0);                    // Clear the LSB bit
      disable_interrupts(INT_EXT);               // Disable the external interrupt
    }
  }
}
#INT_TIMER1                                      // Timer1 interrupt (used for time out)
void timer1_isr(void){
  rc5_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);
  lcd_putc("ADS:0x00  TGL: 0");
  lcd_gotoxy(1, 2);
  lcd_putc("CMD:0x00");
  while(TRUE){
    if(rc5_ok){                                  // If the mcu receives RC5 message with successful
      rc5_ok = 0;                                // Reset decoding process
      rc5_state = 0;
      setup_timer_1(T1_DISABLED);                // Disable Timer1 module
      toggle_bit = bit_test(rc5_code, 11);       // Toggle bit is bit number 11
      address = (rc5_code >> 6) & 0x1F;          // Next 5 bits are for address
      command = rc5_code & 0x3F;                 // The 6 LSBits are command bits
      lcd_gotoxy(16, 1);
      printf(lcd_putc,"%1u",toggle_bit);         // Display toggle bit
      lcd_gotoxy(7, 1);
      printf(lcd_putc,"%2LX",address);           // Display address in hex format
      lcd_gotoxy(7, 2);
      printf(lcd_putc,"%2LX",command);           // Display command in hex format
      enable_interrupts(INT_EXT_H2L);            // Enable external interrupt
    }
  }
}
RC-5 Decoder with PIC16F887 microcontroller video:

Useful link:
http://en.wikipedia.org/wiki/RC-5