RC-5 Decoder

The Philips RC-5 protocol allows control over 32 different devices with 64 different commands per device. Later this protocol was extended to RC-5X, which allows up to 128 different commands per device. Therefore I had to decide to show the IR messages in hexadecimal values, after all we've got only 4 digits on the display.
Addresses are shown on the left two digits and will range from $00 to $1F, while commands are shown on the right two digits and will range from $00 to $7F.

A particular property of the RC-5 protocol is its Toggle bit. This bit will change polarity every time you press a key and will remain unchanged as long as you hold the key. That enables the receiver to detect released keys, which helps eliminate key bounces.
The PIC RC-5 Decoder will indicate the polarity of the Toggle bit on the right most decimal point.

RC-5 Decoder Software

In my knowledge base you can read that the RC-5 protocol uses bi-phase modulation of the IR carrier to transmit a total of 14 bits. A bi-phase modulated bit can be thought of as two separate bits which are always the inverse of each other. A logical zero is represented by a "01" pattern on the IR input, while a logical one is represented by a "10" pattern. That is basically what we're going to use to decode the received message.

RC-5 State Machine
Example of the states of the IR state machine

The IR decoding software relies on a state machine. We've seen a state machine in action in the main software where it was used to multiplex the displays, so we are a bit familiar with it already. Here we use the power of the state machine to break down the entire problem of decoding an IR signal into a few well defined states. Each state has only a limited amount of decisions to make, which greatly simplifies the decoding of the IR signal.
We could have used an alternative technique for multiplexing the displays. However I can't really think of a good alternative for the state machine when it comes to decoding the IR signal.

;-----------------------------------------------------------------------------
;
; IR receiver state machine
;
;-----------------------------------------------------------------------------

IR_MACHINE      MOVF    IR_STATE,W      Jump to present state
                MOVWF   PCL

Here we go! The IR_MACHINE routine is called every 50µs by the main program loop. This reasonable accurate interval can be used to time our steps along the decoding process.
All we do when we arrive at the IR_MACHINE is load the program counter with the current state's starting address. All starting addresses can be found on program page $00, which enables us to leave PCLATCH unchanged.

;---------------------------------------STATE 0, WAIT FOR BEGIN OF START BIT--

IR_STATE_0      BTFSC   PORTA,4         Input still high?
                RETURN                  Yes! Nothing to do

                MOVLW   HALF_TIME/2-1   Wait until we're in the center of the
                MOVWF   BIT_TIMER        start pulse
                MOVLW   IR_STATE_1      Next stop is state 1
                MOVWF   IR_STATE
                RETURN

IR_STATE_0 is called whenever the IR decoder is idle, which means that we are waiting for the start of a new message. This start is signalled by a falling edge on the IR input at PORTA bit 4. So, as long as PORTA pin 4 remains high, there is nothing for us to do but return to the main program loop.
Once we do detect a falling edge on the IR input we set a timer value to the center of half a bit time. Remember that one bi-phase bit can be seen as 2 bits with inversed polarity. All we do here is make sure the next time we check the status of the input is at the center of such a half-bit. Then, finally, we change the state machine to IR_STATE_1, which will be called the next time IR_MACHINE is called from the main program loop.

;---------------------------STATE 1, START BIT DETECTED, CHECK IF IT IS REAL--

IR_STATE_1      DECFSZ  BIT_TIMER       Wait until center of start pulse
                RETURN                  Time's not up yet!

                BTFSC   PORTA,4         Is the input still low?
                GOTO    IR_ERROR_1      Nope! Exit with error

                MOVLW   HALF_TIME       Set interval to the center of the
                MOVWF   BIT_TIMER        first half of the next bit
                MOVLW   %0000.1000      Prepare the shift register
                MOVWF   IR_SHIFT
                CLRF    IR_SHIFT+1
                MOVLW   IR_STATE_2      Prepare for next stop
                MOVWF   IR_STATE
                RETURN

At this state we've detected a falling edge on the IR input, and now we want te be sure that it is a valid starting pulse. We decrement BIT_TIMER until it has reached 0, which brings us right in the center of the starting pulse. There is nothing for us to do as long as BIT_TIMER hasn't reached 0, so we simply return to the main program loop.
Once BIT_TIMER does reach 0 we check the polarity of the IR input. If it is high again we obviously received a false start pulse and jump to the IR_ERROR_1 routine which simply resets the state machine to IR_STATE_0 again.

If the start bit was valid we set BIT_TIMER to half a bit time, which will be right in the center of the first half of the first real bit. Then the shift register is prepared. This shift register will hold the complete IR message after receiving a total of 26 half-bits or 13 real bits. Remember that the start bit has already past us at this stage, so there are only 13 bits of the message left to be handled.
The shift register is loaded with the 16-bit value %0000.0000.0000.1000. Obviously that value has a deeper meaning. Each received bit is shifted in from the right, so after shifting in 13 bits like that the single "1" of the initial value will drop out of the shift register ending up in the Carry flag. That's how we later detect if all bits have arrived. Finally the state machine is updated to IR_STATE_2, which will be executed next time.

;-----------------------------------IR STATE 2, WAIT FOR FIRST HALF OF A BIT--

IR_STATE_2      DECFSZ  BIT_TIMER       Wait until center of first half of bit
                RETURN                  Keep waiting!

                MOVLW   IR_STATE_3      Next state is 3 if input is high
                BTFSS   PORTA,4
                MOVLW   IR_STATE_4      Input is low, next state is 4
                MOVWF   IR_STATE

                MOVLW   HALF_TIME       Restart bit timer
                MOVWF   BIT_TIMER
                RETURN

We're waiting for the the center of the first half of a bit when we enter this state. The BIT_TIMER is decremented until 0 again. We may return to the main program loop if BIT_TIMER hasn't reached 0 yet.
If it did reach 0 we know we are at the center of the first half bit. We read the value of the IR input, and set the next state of the state machine to IR_STATE_3 if the value is high. However if the value of the input is low the state machine is set to IR_STATE_4.
The only thing that's left for us to do in this state is to reload BIT_TIMER again with a value that directs us to the center of the second half of the current bit.

;---------------IR STATE 3, FIRST HALF WAS HIGH NOW IT MUST BE LOW FOR A "1"--

IR_STATE_3      DECFSZ  BIT_TIMER       Wait until center of 2nd half of bit
                RETURN                  Keep waiting!

                BTFSC   PORTA,4         Is input high now?
                GOTO    .ERROR          Nope! It's an error!

                BSF     STATUS,CARRY    A 1 was received, shift it in result
                RLF     IR_SHIFT,F
                RLF     IR_SHIFT+1,F
                MOVLW   HALF_TIME       Restart bit timer
                MOVWF   BIT_TIMER
                MOVLW   IR_STATE_2      In case we need some more bits
                BTFSC   STATUS,CARRY    We're done when Carry is 1
                MOVLW   IR_STATE_5      Carry is 1, received entire message
                MOVWF   IR_STATE
                RETURN

.ERROR          MOVLW   IR_ERROR_0      Wait until input gets high before
                MOVWF   IR_STATE         returning to state 0
                RETURN

I think you already know what the first two lines of this state are for because we've seen them twice before. Then we check the IR input which must be high now, because it was low during IR_STATE_2! If it is not we change the state machine to IR_ERROR_0, where we will wait until the IR input is high again before we can reset the state machine to IR_STATE_0 and start all over again.
If the polarity of the IR input is indeed low now we have received a bit with the value of "1". This bit is then shifted, or actually rolled, into the 16-bit shift register.
Then the bit timer is reloaded to point to the center of the first half of the next bit, in case there is a next bit indeed.
If the Carry flag remains "0" after shifting the new bit in, there must more bits to be received and the state machine is reset to IR_STATE_2. But when the Carry flag is "1" now all bits have been received and the message is complete. Only then we will continue with IR_STATE_5.

;---------------IR STATE 4, FIRST HALF WAS LOW NOW IT MUST BE HIGH FOR A "0"--

IR_STATE_4      DECFSZ  BIT_TIMER       Wait until center of 2nd half of bit
                RETURN                  Keep waiting!

                BTFSS   PORTA,4         Is input high now?
                GOTO    IR_ERROR_1      Nope! It's an error!

                BCF     STATUS,CARRY    A 0 was received, shift it in result
                RLF     IR_SHIFT,F
                RLF     IR_SHIFT+1,F
                MOVLW   HALF_TIME       Restart bit timer
                MOVWF   BIT_TIMER
                MOVLW   IR_STATE_2      In case we need some more bits
                BTFSC   STATUS,CARRY    We're done when Carry is 1
                MOVLW   IR_STATE_5      Carry is 1, received entire message
                MOVWF   IR_STATE
                RETURN

IR_STATE_4 is almost identical to IR_STATE_3. The main differences are the expected polarity of the IR input and the polarity of the received bit. This time the IR input must be high to be valid, in which case we received a "0" bit.
There is one more slight difference and that is when an error occurred. This time the IR input is already high, so we don't have to wait before we can return to IR_STATE_0.

;--------------------------IR STATE 5, MESSAGE RECEIVED, START PROCESSING IT--

IR_STATE_5      MOVLW   CLR_TIME        Set display clear timer
                MOVWF   CLR_DELAY
                MOVLW   DP_TIME         Flash receive LED
                MOVWF   DP_DELAY

                MOVF    IR_SHIFT,W      Get IR command
                ANDLW   %0011.1111      The command is only 6 bits wide
                BTFSS   IR_SHIFT+1,4    Copy inverted extended bit to b6 of
                IORLW   %0100.0000       command

                RLF     IR_SHIFT,F      Shift the entire IR address in
                RLF     IR_SHIFT+1,F     2nd byte of shift register
                RLF     IR_SHIFT,F
                RLF     IR_SHIFT+1,F
                MOVWF   IR_SHIFT        Save cleaned up hex IR command number

                MOVLW   IR_STATE_6      We've done enough in this state
                MOVWF   IR_STATE        Let's do the rest in state 6

                RETURN

We have received the complete IR message now, but we're not done yet. The message must be made presentable for those stupid humans, who are not too good at reading binary data.
First of all the CLR_TIME and DP_TIME counters are set. In this case CLR_TIME is set to 2 seconds and DP_TIME to 60ms. The purpose of these two counters is explained on the software page.
Then the IR command is prepared. Six bits of IR_SHIFT are the main part of the command. RC-5X uses the inverted bit 4 of IR_SHIFT+1 as the 7th command bit, so we have to add that to the command value too. The final value of the IR command is not yet saved back to IR_SHIFT, because IR_SHIFT still holds 2 bits which belong to the IR address.
Next we're shifting the entire shift register 2 bits to the left. That will put the 5 bits of the IR address in IR_SHIFT+1 and will free IR_SHIFT which will now be used to store the IR command.
My guess is that we've done enough during this state, but we are still not ready. Let's continue our quest in IR_STATE_6 to give other tasks a chance to run too.

;------------------------------IR STATE 6, CONVERT HEX MESSAGE TO 7 SEGMENTS--

IR_STATE_6      SWAPF   IR_SHIFT+1,W    Work from left to right
                ANDLW   %0000.0001      Address is only 5 bits wide
                CALL    HEX2SEGMENTS
                MOVWF   DIGIT1

                MOVF    IR_SHIFT+1,W    Do the same with 2nd digit
                CALL    HEX2SEGMENTS
                ANDLW   %0111.1111      Flash dot of this digit
                MOVWF   DIGIT2

                SWAPF   IR_SHIFT,W      And with the 3d digit
                CALL    HEX2SEGMENTS
                MOVWF   DIGIT3

                MOVF    IR_SHIFT,W      And finally with the last digit
                CALL    HEX2SEGMENTS
                BTFSC   IR_SHIFT+1,5    Was the T bit set?
                ANDLW   %0111.1111      Light the dot of this digit if it was
                MOVWF   DIGIT4

                MOVLW   IR_STATE_7      Done enough for now. Let's finish it
                MOVWF   IR_STATE         in the last state

                RETURN

We're almost done. IR_SHIFT+1 holds the received IR address at this point, while IR_SHIFT holds the received IR command. All we need to do now is convert these 2 hex values to 4 seven segment patterns and we can call it a day.
Digit 1 will hold the upper nibble of the IR address. This nibble is only 1 bit wide, so we throw away all the rest (the toggle bit and the 7th command bit are still there). Then we convert that hex nibble to the propper 7 segment pattern and store it in DIGIT1.
Digit 2 is quite similar. This time we don't have to bother about the number of bits because the HEX2SEGMENTS routine will take care of the propper masking. The ANDLW %0111.1111 instruction will switch on the center decimal point to indicate that a valid IR message has been received.
Digit 3 is nothing special. You should be able to understand what happens there.
Digit 4 is a bit special again because the decimal point of this digit should reflect the state of the toggle bit.
Finally we change the state machine to IR_STATE_7, which is the final state. There we'll wait for the IR input to become high again before we can reset the state machine to IR_STATE_0 to be able to start all over again.

;----------------------------------IR STATE 7, WAIT FOR INPUT TO RETURN HIGH--

IR_STATE_7
IR_ERROR_0      MOVLW   IR_STATE_0      Reset state machine only if input is
                BTFSC   PORTA,4          high
                MOVWF   IR_STATE
                RETURN

IR_STATE_7 and IR_ERROR_0 are handled by the same routine. The purpose of this state is to wait for the IR input to return high before the state machine can be reset to IR_STATE_0.

;-----------------------------------------------------------IR ERROR STATE 1--

IR_ERROR_1      MOVLW   IR_STATE_0      Return to IR state 0
                MOVWF   IR_STATE
                RETURN

This is not a state of the IR_MACHINE. We arrive here because one of the states has detected an error while the IR input was high. Because the IR input is already high it is OK te reset the state machine to IR_STATE_0 to start all over again.