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.
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.
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.