Monday, February 7, 2011

Philips RC-5 Infrared Protocol Decoding

Currently developing a circuit that will have as its input device off-the-shelf (TV) remote controls. I initially planned on using the NEC protocol given its inclusion of the complement of the command code which could be used for signal/data integrity checking. However, given that each NEC frame has 32 bits + a leader code (start bit), I decided to use the simpler and less interrupt-service-routine-intensive RC5 protocol by Philips.

The following will be a primer on the method I'm using to decode the RC5 signal. The IR receiver I used for the circuit is the Osram SFH5110-38. This particular part is suppose to be used for IR signals with a 38kHz carrier frequency, but the Philips uses 36kHz. Tests show the Osram has no problem picking up the Philips transmission although I cannot say what improvement in quality of signal output (particularly bit periods) will be if a 36kHz receiver is employed. For the remote control I used a Cybertec RM-312x universal control programmed with setup code 1528 for Philips TV.


Introduction

The Philips RC5 uses bi-phase encoding whereby:
  • logic zero is represented by a falling edge during the middle of the bit period
  • logic one is represented by a rising edge during the middle of the bit period

Here's a summary of the RC5 protocol (this image is purportedly from an original 1992 Philips document on the RC5).



One RC5 bit period = 1.778ms. Half of that is 0.889ms. The start bit is always a logic one and occurs, of course, at the middle of the first bit period. The entire RC5 word is repeated every 114ms while the remote control key is kept pressed. The Toggle/ Control bit remains at the same logic level as long the present key is not released. This bit will toggle if the key is released and another key is pressed or the same key is pressed. The Toggle/Control bit can therefore be used to detect whether the current key is still pressed (required for instance for TV volume control) or the same key has been released and pressed again (required for the input of digits for example).

The above diagram is from the point of view of the IR transmitter wherein the IR LED is normally off (to conserve battery power). The IR receiver output, however, is normally on (output voltage is high while no IR signal is detected). Because of this the output of the receiver will be the complement of the transmitted signal and thus:
  • logic zero is represented by a rising edge
  • logic one is represented by a falling edge

The receiver has circuitry to filter out the 36kHz carrier wave and thus its output is clean and doesn't have the 36kHz "spikes."

From the point of view of the IR receiver output here are some important facts we can infer from the RC5 protocol:
  • F1. A level change must occur during the middle of every bit period. 
  • F2. A level change may or may not occur during the start/end of a bit period. 
  • F3. Half-bit-period level changes (i.e., the elapsed time between two level changes is 0.889ms) always occur in pairs, with the latest/current level change occurring at the middle of a bit period. 
  • F4. Given a pair of half-bit-period level changes, the current bit has the same value as the previous bit, i.e., if the value of previous bit was a logic one then the value of the current bit is a logic one, and the current bit is a logic zero if the previous bit was a logic zero. 
  • F5. Given a full-bit-period level change (i.e., the elapsed time between two level changes is 1.778ms), the current level change occurred at the middle of a bit period, and the value of the current bit will be the complement of the value of the previous bit, i.e., if the previous bit was a logic one then the current bit is a logic zero, and the current bit is a logic one if the previous bit was a logic zero.  
One way to decode a RC5 transmission is to check the direction of the level change during the middle of each bit period--whether it is a falling or rising edge. Given F2 we need a way to know whether it's the start/end or middle of a bit period. Since the start bit (a falling edge) occurs at the middle of the first bit period, we can use that as a reference point, confident that every 1.778ms thereafter--every full bit period or every two half bit periods--will be the middle of the succeeding bit periods.


MCU Implementation

The test circuit consisted of a Microchip PIC16F883 microcontroller using its internal RC clock running at 4MHz, an Osram SFH5110-38 infrared receiver, and four discrete LEDs as indicators to see if the IR signal was being detected and whether the transmitted command codes were being correctly decoded. The circuit was powered by an ATX supply since I discovered via oscilloscope that the PICkit 2 apparently didn't have enough juice to power the IR receiver--even with a 1uF tantalum or 10uF aluminum cap at the VS pin of the IR receiver its output would sporadically spike down to low (zero volts) even with no signal being received. And when signal was received its output was garbage.

Because level changes (falling and rising edges) need to be detected the output pin of the IR receiver is connected to an MCU pin that has interrupt-on-change (IOC) capability. In this case I chose the 883's RB0 pin. With the proper special function registers (SFR) set up, an interrupt will occur whenever a level change occurs--whenever voltage at the RB0 changes from high to low or from low to high.

A timer is set up so the elapsed time between successive IOCs can be measured. If it's ~0.889ms then we know half a bit period has passed. If it's ~1.778ms then elapsed time is a full bit period. I used Timer0 with a prescaler value of 1:16. Given the 1µs instruction cycle, register TMR0 will overflow every 256 x 16 x 1µs = 4.096ms (INTCON.T0IF will be set even if interrupt is not enabled).

The start of a RC5 word is the first falling edge detected after the IR receiver has been high for over 4.096ms--when Timer0 overflows and its interrupt flag gets set. This falling edge indicates the start bit. The start bit is not recorded/stored. It is used only as the reference for determining the middle of the bit periods. This falling edge occurs at the middle of the first bit period of the word, so every full bit period (1.778ms) after this is the middle of a bit period.

When an IOC occurs and the measured elapsed time from the last IOC is half a bit period long, given there was no prior half bit period or the previous half bit period has already been paired, then according to F3 the very next IOC will occur after half a bit period. That will then be the middle of a bit period. Checking the direction of the level change provides the bit value. Alternately, as per F4 that bit will have the same value as the previous bit.

When an IOC occurs and the measured elapsed time from the last IOC is a full bit period then the current IOC has occured at the middle of a bit period and the bit value (0 or 1) is recorded, depending on the direction of the level change. Because of F5, instead of checking the direction of level change, the value of current bit can be obtained by looking at the value of the previous bit.

Because the RC5 word is transmitted MSB first, we left shift the incoming bits into their storage variables (registers). Because I will be using the Command codes and the Control bit but won't for now be using the Address bits I've decided to store the Command bits in one variable and the rest in another.
 
As the Field bit, Control bit, and 5-bit System Address are received they are left-shifted into variable RC5FCSYS. Thus when all the bits are in, the Field bit and Control bit will be in bits 6 and 5 of RC5FCSYS, respectively, and the MSB and LSB of the System Address will be in bits 4 and 0 of RC5FCSYS, respectively.

The 6-bit Command bits are left-shifted into RC5COMM in the order they are received by the IR receiver. Thus, after all the bits are in, the MSB and LSB of the Command code will be in bits 5 and 0, respectively.

Variable RC5BITS keeps track of the number of bits already received and is used to determine into which of the two variables the current bit will be stored. 

Because the bit period of the incoming signal will never be exactly as per specifcation and because the MCU is running on its internal RC clock with limited accuracy, we need to predetermine a range of incoming IR pulse widths that are accepted as legitimate half and full bit periods. Any received pulse width outside these ranges will be considered an indication of an error and the entire RC5 word will be disregarded.

An oscilloscope can be used to sample several half and full bit periods from various remote controls (IR transmitters) used at various distances from the IR receiver. The Cybertec remote control used to test the firmware had half bit periods that were within ±20% and full bit periods within ±10%. Using 20% the ranges are: half bit period: 0.7112ms to 1.0668ms, full bit period: 1.4224ms to 2.1336ms. Those values  were converted to 8-bit integers taking into account the MCU clock frequency and timer prescale value.

After the firmware had been debugged, the response of the MCU to IR signals was found to be unreliable and intermittent--there was noticeable inability to detect valid signals around 50% of the time. So I increased the tolerance to ±25%. Thus the acceptable ranges of values increased to 0.66675 to 1.11125ms and 1.3335 to 2.2225ms. With that increased range the MCU picked up the transmission every time. Transmitter was tested up to 4 meters and within 45degrees of the receiver (zero degrees is perpendicular to the face of the receiver).

Note that if tolerance is uniform for both upper and lower limits and for half and full bit periods then the maximum tolerance allowable is 33% since with a value >33% the half bit period upper limit will be greater than the full bit period lower limit: 0.889ms + 0.889x > 1.778ms - 1.778x, with x > 1/3. Tolerances needn't be uniform, of course. If, as with the Cybertec remote control, the full bit period tolerances are much better than the half bit period's, then the range for the full bit period can be decreased accordingly.

Here's the pertinent part of the RC5 decoding firmware. It was written in mikroC Pro for PIC.


/*

Philips RC5 Decoding Routine
February 2011
Edwardson Tan 
 
processor = PIC16F883
*/

#define  irr                 PORTB.f0  // Osram SFH5110-38 infrared receiver
#define  tris_irr            TRISB.f0  // for use with setting the appropriate pin as input
   
// RC5 decoding defines
#define  rc5_on              FEN.f0              // 1 = irr output has been high for >4millisec and then goes low; this indicates a RC5 start bit
                                                 // 0 = irr output has been low for >4millisec and then goes high. This is an abnormal state and is an error
                                                 // rc5_on is also reset to 0 when all bits of word have been received
#define  halfper_prev        FEN.f1              // 1 = the last IOC was a half bit period, 0 = the last IOC occured after a full bit period
#define  button_pressed      FEN.f2              // 1 = button has been pressed and valid RC5 word stored
#define  rc5_read_error      FEN.f3              // 1 = read error, pulse width of either a zero or one bit is outside the acceptable limits; the data packet should be discarded
#define  bit_period          FEN.f4              // _full = 1 = one full bit period, _half = 0 = half bit period
#define  curr_bit            FEN.f5              // contains the latest decoded value of the bit of the RC5 word being received

#define  _full               1
#define  _half               0

// one full bit period for RC5 = 1.778ms; one half bit period = 0.889ms
// given clock = 4MHz and TMR0 initial value = 0 and timer0 prescale = 1:16, TMR0 overflows every 256 x 16 / 1MHz = 4.096ms
// 4.096ms / TMR0 count = 4.096 / 256 = 0.016ms per TMR0 count
// 1.778 / 0.016ms = 111   this is the TMR0 count when time elapsed is 1.778ms
#define  _fullbit            111                                     // full bit period of RC5
#define  _tolerance          25                                      // tolerance in percent, to be added/subtracted to/from half and full bit periods to create range of acceptable pulse widths
                                                                     // !! THIS HAS TO BE INTEGER NOT FLOATING POINT !!
                                                                     // !! MAXIMUM VALUE = 33%, ELSE halfbit_uplim WILL BE GREATER THAN fullbit_uplim

#define  _halfbit            _fullbit / 2                            // half bit period of RC5

#define  halfbit_lolim       _halfbit - (_halfbit * _tolerance / 100) // minimum pulse duration for half bit period
#define  halfbit_uplim       _halfbit + (_halfbit * _tolerance / 100) // maximum pulse duration for half bit period
#define  fullbit_lolim       _fullbit - (_fullbit * _tolerance / 100) // minimum pulse duration for full bit period
#define  fullbit_uplim       _fullbit + (_fullbit * _tolerance / 100) // maximum pulse duration for full bit period


// ===========================================================================================
//       Global Variables
// ===========================================================================================

int8     FEN = 0;            // Flag and ENable and status bits register
int8     PULSEWIDTH;         // contains the measured pulse width (to get the real value in millisec multiply by 16)
int8     RC5BITS;            // contains the bit number (excluding the start bit) of the RC5 word currently being received
int8     RC5FCSYS;           // contains the RC5 Field bit, Control Bit, and the 5-bit System Address
int8     RC5COMM;            // contains the RC5 6-bit Command code
int8     TEMP;               // temporary memory


// ===========================================================================================
//       Functions
// ===========================================================================================

void InitRegisters()
{
  PORTA = 0;
  PORTB = 0;
  PORTC = 0;

  TRISA = 0;
  TRISB = 0;
  TRISC = 0;
  ANSEL = 0;
  ANSELH = 0;
  
  tris_irr = 1;
  
  OPTION_REG = 0b10000011;   // prescaler assigned to timer0
                             // prescale = 1:16
                             // timer0 uses internal clock instruction cycle
                             // weak pull ups disabled
                             
  // enable interrupt on change
  // intialize RB0/INT pin to interrupt on input change -- pin is connected to IR receiver
  INTCON.RBIE = on;
  IOCB = 0b1;
  
  INTCON.GIE = 1;
  INTCON.PEIE = 1;
  
} // void InitRegisters()

// ===========================================================================================
//       ISR
// ===========================================================================================

void interrupt()
{
  // interrupt on change
  if (INTCON.RBIF)
  {
    PULSEWIDTH = TMR0;
    TMR0 = 0;
    TEMP = PORTB;            // a read of PORTB is necessary before RBIF can be cleared
    INTCON.RBIF = 0;

    if (rc5_on && !rc5_read_error)
    {
      if (PULSEWIDTH >= halfbit_lolim && PULSEWIDTH <= halfbit_uplim)
        bit_period = _half;
      else if (PULSEWIDTH >= fullbit_lolim && PULSEWIDTH <= fullbit_uplim)
        bit_period = _full;
      else
        rc5_read_error = 1;

      if (bit_period == _half && halfper_prev == 0)
      {
        halfper_prev = 1;    // last IOC occured after a full bit period, so this current half bit period needs to be paird with the half bit period
      }
      else // if (bit_period == _full || halfper_prev == 1)
      {
        halfper_prev = 0;
        if (irr)             // rising edge, indicates logic zero
          curr_bit = 0;
        else                 // falling edge, indicates logic one
          curr_bit = 1;

        if (RC5BITS <= 6)
        {
          RC5FCSYS <<= 1;
          RC5FCSYS.f0 = curr_bit;
        }
        else
        {
          RC5COMM <<= 1;
          RC5COMM.f0 = curr_bit;
        }
        if (++RC5BITS >=13)
        {
          rc5_on = 0;
          button_pressed = 1;
        }
      }
    } // if (rc5_on && !rc5_read_error)

    // if timer0 interrupt flag is set then ir_rx output was low/high for >4ms before the IOC that just occurred
    if (INTCON.T0IF)
    {
      INTCON.T0IF = 0;
      RC5BITS = 0;
      RC5FCSYS = 0;
      RC5COMM = 0;
      rc5_read_error = 0;
      rc5_on = 0;
      if (!irr)                        // falling edge; pulse was high for >4ms so this is considered the start bit of RC5 word
        rc5_on = 1;
    }
  } // if (INTCON.RBIF)
} // void interrupt()


----

References:

AN10210 Using the Philips 87LPC76x microcontroller as a remote control transmitter
Philips RC-5 Protocol

No comments:

Post a Comment