This post shows how to decode IR (Infra-Red) remote control that uses the Philips RC5 protocol using Microchip PIC16F887 8-bit microcontroller. Decoding results are printed on 16×2 LCD screen.
The compiler used in this project is Microchip MPLAB XC8 (MPLAB X IDE with MPLAB XC8 compiler).
About RC5 protocol:
The RC-5 protocol was developed by Philips in the late 1980s as a semi-proprietary consumer IR (infrared) remote control communication protocol for consumer electronics.
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 have the same address but each button has its command.
The toggle bit changes whenever a button is pressed.
The RC5 protocol uses Manchester coding, logic 1 and logic 0 are coded as shown in the following drawing where toggle bit is 1, address = 0 and command is 2:
This Wikipedia page contains more details about the RC-5 protocol.
Related Projects:
To see how to interface PIC microcontroller with LCD module using MPLAB XC8 compiler, read the following post:
Interfacing LCD with PIC microcontroller | MPLAB Projects
NEC Remote control decoder with PIC microcontroller | MPLAB Projects
Hardware Required:
- PIC16F887 microcontroller —-> datasheet
- IR Receiver
- 16×2 LCD screen
- 10k ohm variable resistor or potentiometer
- 330 ohm resistor
- 5V Power source
- Protoboard
- Jumper wires
- PIC microcontroller programmer (PICkit 2, PICkit 3…)
RC5 Remote control decoder with PIC microcontroller circuit:
The following image shows project circuit diagram.
(All grounded terminals are connected together)
Generally the IR receiver has 3 pins: GND, VCC and data. The data pin is connected to pin RB0 of the PIC16F887 microcontroller.
The 16×2 LCD screen is connected to the PIC16F887 microcontroller as follows:
RS —> RD0 pin
E —> RD1 pin
D4 —> RD2 pin
D5 —> RD3 pin
D6 —> RD4 pin
D7 —> RD5 pin
VSS, RW, D0, D1, D2, D3 and K are connected to circuit GND (ground)
VEE to the variable resistor (or potentiometer) output pin
VDD to +5V and A to +5V through 330 ohm resistor
VEE pin is used to control the contrast of the LCD. A (anode) and K (cathode) are the back light LED pins.
In this project the PIC16F887 microcontroller runs with its internal oscillator @ 8 MHz, MCLR pin is configured as an input pin.
The following drawing shows how the IR receiver receives (infra-red signals that comes from the remote control) and transmits data to the microcontroller:
NEC Remote control decoder with PIC microcontroller C code:
The C code below is for MPLAB XC8 compiler, it was tested with version 2.00 installed on MPLAB X IDE version 5.05.
To be able to compile the C code, a small LCD library for MPLAB XC8 compiler is required which can be downloaded from the following link:
MPLAB XC8 LCD Library
after the download, add the library file (LCD_Lib.c) to project folder.
Programming hints:
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.
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.
The output of the IR receiver is connected to external interrupt pin (RB0) (interrupt on change) and every change in the pin status generates an interrupt and Timer1 starts ticking, Timer1 value will be used in the next interrupt, this means Timer1 measures 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 long 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 microcontroller used in this example is PIC16F887, configuration words are:
1 2 | #pragma config CONFIG1 = 0x2CD4 #pragma config CONFIG2 = 0x0700 |
Where:
- In-Circuit Debugger disabled
- Low voltage programming disabled
- Fail-Safe Clock Monitor enabled
- Internal/External Switchover mode enabled
- Brown-out Reset (BOR) disabled
- Data memory code protection disabled
- Program memory code protection disabled
- RE3/MCLR pin function is digital input, MCLR internally tied to VDD
- Power-up Timer (PWRT) disabled
- Watchdog Timer (WDT) disabled
- INTOSCIO oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN
- Flash Program Memory Self Write disabled
- Brown-out Reset set to 4.0V
Full MPLAB XC8 code:
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | /* * RC-5 Protocol IR remote control decoder with PIC16F887 MCU. * C Code for MPLAB XC8 compiler. * Internal oscillator used @ 8MHz. * This is a free software with NO WARRANTY. * https://simple-circuit.com/ */ // set configuration words #pragma config CONFIG1 = 0x2CD4 #pragma config CONFIG2 = 0x0700 //LCD module connections #define LCD_RS RD0 #define LCD_EN RD1 #define LCD_D4 RD2 #define LCD_D5 RD3 #define LCD_D6 RD4 #define LCD_D7 RD5 #define LCD_RS_DIR TRISD0 #define LCD_EN_DIR TRISD1 #define LCD_D4_DIR TRISD2 #define LCD_D5_DIR TRISD3 #define LCD_D6_DIR TRISD4 #define LCD_D7_DIR TRISD5 //End LCD module connections #include <xc.h> #define _XTAL_FREQ 8000000 #include <stdio.h> // for sprintf #include <stdint.h> // include stdint header #include "LCD_Lib.c" // include LCD driver source file #define short_time 700 // used as a minimum time for short pulse or short space #define med_time 1200 // used as a maximum time for short pulse or short space #define long_time 2000 // used as a maximum time for long pulse or long space char text[3]; __bit rc5_ok; uint8_t rc5_state, bit_n; // bit_n: bit number used for variable rc5_code bits uint16_t rc5_code, timer_value; // rc5_code: whole RC5 code message // interrupt ISRs void __interrupt() MyISRs(void) { /*************** start external interrupt ISR ***************/ if (RBIF && (RB0 || !RB0)) // PORTB change ISR (& clear mismatch condition) { RBIF = 0; // clear PORTB interrupt flag bit if(rc5_state != 0) { timer_value = (TMR1H << 8) | TMR1L; // store Timer1 value TMR1H = TMR1L = 0; // reset Timer1 } switch(rc5_state) { case 0 : // start receiving IR data (initially we're at the beginning of mid1) TMR1H = TMR1L = 0; // reset Timer1 TMR1ON = 1; // enable Timer1 rc5_state = 1; // next state: end of mid1 bit_n = 0; // reset bit number break; case 1 : // end of mid1 ==> check if we're at the beginning of start1 or mid0 if((timer_value > long_time) || (timer_value < short_time)) { // invalid interval ==> stop decoding and reset rc5_state = 0; // reset decoding process TMR1ON = 0; // disable Timer1 } else { rc5_code |= 1 << (13 - bit_n); // set bit (13 - bit_n) bit_n++; if (bit_n > 13) // if all bits have been received { rc5_ok = 1; // decoding process is OK RBIE = 0; // disable PORTB change interrupt } else { if(timer_value > med_time) // we're at the beginning of mid0 { rc5_state = 2; // next state: end of mid0 if(bit_n == 13) // if we're at the LSB bit { rc5_ok = 1; // decoding process is OK rc5_code &= ~1; // clear the LSB bit RBIE = 0; // disable PORTB change interrupt } } // end " if(timer_value > med_time) " else // we're at the beginning of start1 rc5_state = 3; // next state: end of start1 } // end " else " } // end " else " break; case 2 : // end of mid0 ==> check if we're at the beginning of start0 or mid1 if((timer_value > long_time) || (timer_value < short_time)) { // time interval invalid ==> stop decoding rc5_state = 0; // reset decoding process TMR1ON = 0; // disable Timer1 } else { rc5_code &= ~(1 << (13 - bit_n)); // clear (13 - bit_n) bit_n++; if(timer_value > 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 } break; case 3 : // end of start1 ==> check if we're at the beginning of mid1 if((timer_value > med_time) || (timer_value < short_time)) { // time interval invalid ==> stop decoding TMR1ON = 0; // disable Timer1 rc5_state = 0; // reset decoding process } else // we're at the beginning of mid1 rc5_state = 1; // next state: end of mid1 break; case 4 : // end of start0 ==> check if we're at the beginning of mid0 if((timer_value > med_time) || (timer_value < short_time)) { // time interval invalid ==> stop decoding TMR1ON = 0; // disable Timer1 rc5_state = 0; // reset decoding process } else // we're at the beginning of mid0 { rc5_state = 2; // next state: end of mid0 if(bit_n == 13) // if we're at the LSB bit { rc5_ok = 1; // decoding process is OK rc5_code &= ~1; // clear the LSB bit RBIE = 0; // disable PORTB change interrupt } } // end " else " } // end " switch(rc5_state) " } // end " if (RBIF && (RB0 || !RB0)) " /*************** end external interrupt ISR ***************/ /*************** start Timer1 ISR ***************/ if (TMR1IF) // Timer1 ISR { TMR1IF = 0; // clear Timer1 interrupt flag bit rc5_state = 0; // reset decoding process TMR1ON = 0; // disable Timer1 } /*************** end Timer1 ISR ***************/ } /*************************** main function *********************/ void main(void) { OSCCON = 0x70; // set internal oscillator to 8MHz ANSELH = 0; // configure all PORTB pins as digital TMR1IF = 0; // clear Timer1 overflow interrupt flag bit RBIF = 0; // clear PORTB change interrupt flag bit TMR1IE = 1; // enable Timer1 overflow interrupt T1CON = 0x10; // set Timer1 clock source to internal with 1:2 prescaler (Timer1 clock = 1MHz) INTCON = 0xC8; // enable global, peripheral and PORTB change interrupts IOCB0 = 1; // enable RB0 pin change interrupt rc5_ok = rc5_state = 0; // reset decoding process __delay_ms(1000); // wait 1 second LCD_Begin(); // initialize LCD module LCD_Goto(1, 1); // move cursor to column 1, row 1 LCD_Print("ADS:0x00 TGL: 0"); LCD_Goto(1, 2); // move cursor to column 1, row 2 LCD_Print("CMD:0x00"); while(1) { while (!rc5_ok); // wait until NEC code receiver rc5_ok = rc5_state = 0; // reset decoding process TMR1ON = 0; // disable Timer1 uint8_t toggle_bit = (rc5_code >> 11) & 1; // toggle bit is bit number 11 uint8_t address = (rc5_code >> 6) & 0x1F; // next 5 bits are for address uint8_t command = rc5_code & 0x3F; // the 6 LSBits are command bits LCD_Goto(16, 1); // move cursor to column 16 line 1 LCD_PutC(toggle_bit + '0'); // print toggle bit sprintf(text,"%02X",address); LCD_Goto(7, 1); // move cursor to column 7 line 1 LCD_Print(text); // print address sprintf(text,"%02X",command); LCD_Goto(7, 2); // move cursor to column 7 line 2 LCD_Print(text); // print command RBIE = 1; // enable PORTB change interrupt } } /*************************** end main function ********************************/ |
RC5 Remote control decoder with PIC microcontroller video:
The following video shows the decoding of a RC5 protocol based TV remote control, button values are printed on 1602 LCD screen.
Discover more from Simple Circuit
Subscribe to get the latest posts sent to your email.
Thanks for tis. Works perfectly. On my LCD It was necessary to not connect the pins D0 to D3. With a ground connection, there was nothing displayed.