JVC Decoder

The JVC protocol allows control of up to 256 different devices with 256 different commands per device. On my PIC IR decoder addresses are shown on the left two digits, while commands are shown on the right two. The device address and the command number are both displayed in hexadecimal format.

Only the first message is preceded by a 8.4ms pre-pulse while all repetitions of the that code are not. To us this pre-pulse adds no significant value and is therefore completely ignored by the software.

JVC Decoder Software

In my knowledge base you can read that the JVC protocol uses pulse distance modulation of the IR carrier to transmit a total of 16 bits. A logical zero is represented by an interval of 1.05ms, while a logical one is represented by double that value. I'm going to use the average value between these intervals as a threshold to determine whether a "0" or a "1" was received. Any interval shorter than 1.5ms is interpreted as a "0" and intervals above 1.5ms are interpreted as a "1". Intervals longer than 3ms cause the routine to exit with an error condition.
This loose checking of the intervals makes the decoding process a lot easier, but it may happen that the receiver listens to foreign protocols too. In our case this is not very important. But if you intend to control more critical devices you should check the interval boundaries a little more carefully. A good alternative would be to check whether the intervals are within a ±10% range of the nominal values.

JVC State Machine
Example of the states of the IR state machine

Again the IR decoding software relies on a state machine. The picture above shows all different states during the decoding process.

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

IR_MACHINE      MOVF    IR_STATE,W      Jump to present state
                MOVWF   PCL

Here we go again! Nothing new here. As usual the IR_MACHINE routine is called every 50µs by the main program loop.

;---------------------------------------STATE 0, WAIT FOR FIRST FALLING EDGE--

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

                CLRF    IR_SHIFT        Prepare shift register
                MOVLW   %1000.0000
                MOVWF   IR_SHIFT+1
                CLRF    BIT_TIMER       Clear bit timer
                MOVLW   IR_STATE_1      Next stop is state 1
                MOVWF   IR_STATE
                RETURN

As long as the input remains high we return immediately. Otherwise the IR shift register, which will hold the received message, is initialized. The "1" in the most significant bit of the shift register will roll out after 16 shifts, indicating the end of the message. This will set the Carry flag.
BIT_TIMER is cleared to 0, and will increment every 50µs. Obviously this timer is going to be used as pulse distance timer.

;---------------------------------------------STATE 1, FALLING EDGE DETECTED--

IR_STATE_1      INCF    BIT_TIMER       Increment bit timer
                BTFSC   PORTA,4         Input still low?
                GOTO    .HIGH           No! Pulse is over now.

                MOVLW   PULSE_MAX       See if max pulse width reached
                XORWF   BIT_TIMER,W
                BTFSS   STATUS,ZERO
                RETURN                  Not yet!

                MOVLW   IR_ERROR_0      Wait until pulse goes high again
                MOVWF   IR_STATE         before accepting new command
                RETURN

.HIGH           MOVLW   IR_STATE_2      Input is now high
                MOVWF   IR_STATE        Next stop is state 2
                RETURN

We arrive at state 1 when a low level on the input was detected and we will stay here until the input goes high again or until the maximum pulse width is reached.
First of all we increment BIT_TIMER. Then we check to see if the input is still low. If it's not we jump to label .HIGH.
As long as the input remains low we check to see if the maximum pulse width of about 650µs is exceeded. This may be the case if we are currently receiving the 8.4ms long pre-pulse. We've decided to ignore that pulse, and that is what we're going to do in the IR_ERROR_0 state of the state machine. Otherwise we'll stay in state 1.
In case the input has gone high we continue with state 2.

;-------------STATE 2, WAIT FOR PULSE TO GO LOW AGAIN, THEN MEASURE INTERVAL--

IR_STATE_2      INCF    BIT_TIMER       Increment bit timer
                MOVLW   BIT_TIME*3      Should we keep waiting?
                XORWF   BIT_TIMER,W
                BTFSC   STATUS,ZERO
                GOTO    IR_ERROR_1      No, we've waited 3 bit times already!

                BTFSC   PORTA,4
                RETURN                  Keep waiting while input remains high

                MOVLW   BIT_TIME/2+BIT_TIME*-1  Use 1.5 bit time as threshold
                ADDWF   BIT_TIMER,W     Carry is 1 if more than 1.5 times

                RRF     IR_SHIFT+1,F    Roll carry into result
                RRF     IR_SHIFT,F

                CLRF    BIT_TIMER       Restart bit timer
                MOVLW   IR_STATE_1      Return to state 1 if not done yet
                MOVWF   IR_STATE

                BTFSS   STATUS,CARRY    See if we're done
                RETURN                  Not done yet, continue with state 1

                MOVLW   IR_STATE_3      Message received, but wait 3ms longer
                MOVWF   IR_STATE         to see if nothing follows.
                CLRF    BIT_TIMER
                RETURN

Again we begin by incrementing BIT_TIMER. Then we check to see if BIT_TIMER has reached its upper limit of 3 bit times. Clearly that would indicate an error situation and causes us to jump to IR_ERROR_1.
Then we check to see if the input is still high, which it is in between two adjacent pulses. If it is we can simply return to the main program loop again.
But if the input has gone low we've arrived at the next pulse and we can now evaluate BIT_TIMER. I already said that I use a threshold value of 1.5 times the "0" bit time. Because the SB-Assembler can handle only integers the formula to do this calculation may seem a bit odd. First we do BIT_TIME/2 and add BIT_TIME to that value, which is the same as 1.5 times BIT_TIME. At the end we multiply the entire value with -1 to get a negative number which can be added to BIT_TIMER. Thus actually this addition is a subtraction. Please note that the SB-Assembler knows nothing about mathematic precedence and will perform calculations in the order in which they appear in the formula.
Subtracting 1.5 bit-time from BIT_TIMER will cause the Carry to be set only if BIT_TIMER was larger than 1.5 bit-time. This Carry is then simply rolled into the IR_SHIFT registers from the left to build up the end result.

Since we've arrived at the next pulse now we must clear BIT_TIMER before we can measure the next interval. And if more bits are to follow we must continue with state 1 again.
We know that more bits follow if the shifting process left the Carry cleared. Otherwise we forget about state 1 and will continue with state 3, where we are going to make sure no more bits follow.

;-------------------STATE 3, RECEIVED MESSAGE, WAIT 3MS TO ENSURE IT WAS JVC--

IR_STATE_3      INCF    BIT_TIMER       Increment bit timer
                BTFSC   PORTA,4         Input still low?
                GOTO    .HIGH           No! Pulse is over now.

                MOVLW   PULSE_MAX       See if max pulse width reached
                XORWF   BIT_TIMER,W
                BTFSS   STATUS,ZERO
                RETURN                  Not yet!

                MOVLW   IR_ERROR_0      Wait until pulse goes high again
                MOVWF   IR_STATE         before. accepting new command
                RETURN

.HIGH           MOVLW   IR_STATE_4      Input is now high
                MOVWF   IR_STATE        Continue with 3ms delay
                RETURN

After receiving the entire message we must make sure that no more bits follow. At first I didn't have this check and it appeared that the receiver also listened to NEC remote controls, which is not desirable because it only displayed erratic codes.
I want to state that any similarities with state 1 are purely coincidental! Of course they are not. All I want to do now is wait until the last pulse ends, just like we did in state 1. When the input goes high we go to state 4, where we wait 3ms before finally accepting the received code.

;---------------------------STATE 4, INPUT MUST REMAIN HIGH FOR AT LEAST 3MS--

IR_STATE_4      INCF    BIT_TIMER       Increment bit timer

                BTFSS   PORTA,4         Has the input gone low now?
                GOTO    .ERROR          Yes! This is not good!

                MOVLW   BIT_TIME*3      Have we waited long enough now?
                XORWF   BIT_TIMER,W
                BTFSS   STATUS,ZERO
                RETURN                  Not yet!

                MOVLW   CLR_TIME        Set display clear timer
                MOVWF   CLR_DELAY

                MOVLW   DP_TIME         Flash receive LED
                MOVWF   DP_DELAY

                SWAPF   IR_SHIFT,W      Work from left to right
                CALL    HEX2SEGMENTS
                MOVWF   DIGIT1

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

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

                MOVF    IR_SHIFT+1,W    And finally with the last digit
                CALL    HEX2SEGMENTS
                MOVWF   DIGIT4

                MOVLW   IR_STATE_0      We're done! Let's get some rest
                MOVWF   IR_STATE
                RETURN

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

The first few lines of this state are responsible for ensuring that no more bits follow the received code, which would tell us that we're not receiving a JVC protocol. Once we are certain that nothing follows we can finally display the received message.

We've seen all this before. So I'm not going to explain it again. However there is one little thing I should mention here. Because of the very fast repetition rate of the JVC code I had to decrease the value of DP_TIME to make the IR receive dot flicker again. However the value is now very low and may cause the dot to flicker in an irregular manner. This is just caused by the interference between the repetition rate of the IR messages and the display scan frequency. It doesn't affect the actual reception of the message.

;----------------------------------IR ERROR 0, WAIT FOR INPUT TO RETURN HIGH--

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

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

IR_ERROR_1      MOVLW   IR_STATE_0      Return to IR state 0
                MOVWF   IR_STATE

                RETURN

Should I really explain these two routines again? We've seen them a couple of times before.