Search This Blog

Tuesday, December 20, 2011

IR Remote Control

So, I bought this:
                                                        



   It's an IR Receiver module that operates on IR signals modulated at 38 kHz.  It has three terminals for Vcc, Ground, and signal Out.  The Out signal is normally high ( digital one ).  When it detects a pulse modulated width signal with a period of 38 kHz ( with a duty cycle somewhere around 1/3 to 1/2 ), the out signal goes low ( digital 0 ).  This component has a great range and arc, and it ignores just about anything else around it.  It's typical of the IR receiver on your TV, stereo, etc. 

   I had an old JVC stereo remote, and I wanted to see if I could decode its signals with this component on my Pic 16F88.  I did a bit of Google research on IR protocols.  The first thing to note in hindsight: If you can, use a Sony remote!!  Their protocol is by far the easiest one I found.  It has clear, easy to detect boundaries that all focus on what will be the low signals you receive.  That said, I had a JVC remote lying around that wasn't connected to anything I needed.  The information on this protocol was not as clear as it could have been.  I'll attempt to clear some of that up here if I can. 

   JVC uses space-width codification for its transmission.  This means that the length of time a signal is high or low is of great importance.  Everything is based on a basic time unit, T.  For JVC, this time interval is defined as 527 us +/- 60 us.  For my tests,
we split the difference and assume that T is 560 us.  From the anals of days past, the protocol wants to send a start sequence.  This sequence was intended to help receivers calibrate themselves to the sender.  It is basically unpredictable when this sequence will be sent.  Every remote seems different.  We will eventually ignore it.  The bulk of a send consists in 16 bits ( 2 bytes or 1 word ) of information sent.  The first 8 bits is the address.  This is an identifier for the device.  The next 8 bits is the command.  Both of these bytes are sent Least Significant Bit first.  Here's the tricky part:  The documentation I found on the Web doesn't say much about the end of the packet.  There IS A STOP BIT!!! More on that in a bit.

   From the point of view of the IR component ( and microcontroller ), the signal is usually high.  The tendency is to want to pay attention to the low signals.  With Sony, this is true and easy.  With JVC, the time significant signals are high is what is
important.  It turns out that there are 2 low "marks" and 4 high "spaces" to watch for:

-----------------------
       Times
-----------------------

Low Marks:
----------
Header Mark:    8.44 ms
Bit Marks:         527 us   ( T )

High Spaces:   
------------
Header Space:    4.22 ms
Zero:                   527 us   ( T )
One:                    1.58 ms ( 3T )
Word Space:      12.53 - 29.54 ms


   Here's what a transmission looks like from the point of view of a microcontroller:




   Every bit begins with the line going low for time T.  The ONLY time the line ever goes low is to signal either a header or the start of a bit.  The trick is to know where we are in a transmission.  Retransmits ( any transmit where we don't have a header ) just start firing bits.  We need to know that we are at the FIRST bit of the tranmission.  The only way to be sure of this is to make sure that no bit transmission stays high too long. 

   Here's how it breaks down.  We look for the signal to go low.  We wait for it to go high.  We wait for 1.5 T.  If the signal has gone low, we have a zero transmitted.  We are halfway into the bit mark for the next bit.  If the signal is still high, we wait 2T ( total time elapsed 3.5T ). If the signal has gone low, we have a one and we are halfway into the bit mark for the next bit.  If it is still high, we are in either the header or the wait time after a stop bit.  Either way, we are mis-aligned, so we return and wait for a better start position. 

   The stop bit is a normal pull low for time T.  Then, a time of 12.53 - 29.54 ms will be high. 

   Here's what works:

Signal goes low, call getIrData.

getIrData
    ; setup, init values
    movlw    0x80                            ; load dataPacket with 128.
    movwf    dataPacket
    clrf    addressPacket
getBitLoop
    ; Wait for line to go high
    btfss    irPortBit
        goto    getBitLoop

testIrHighTime
    ; wait for 1.5 T
    call    delay700us
    btfss    irPortBit
        goto    bitIsAZero    ; shift in a zero
    ; now wait 2T. This will either put us .5T into the start mark of the next bit, or an error
    call    delay1ms
    btfss    irPortBit
        goto    bitIsAOne
    ; Here, we are in a misalignment zone.  We are either in the high time of a start bit
    ; or in the rest time between repeats.  Clean up and return.
    return

bitIsAZero
    ; Signal is now low.
    ; bit is a zero. rotate zero in.
    bcf        STATUS,C
    rrf        dataPacket,F
    rrf        addressPacket,F
    ; if Carry is set, we've acquired 16 bits and we are done.
    btfss    STATUS,C
        goto    getBitLoop
    ; if it is set, go wait for the stop bit.
    goto    waitForStopBit

bitIsAOne
    ; Signal is now low.
    ; Bit is a one. rotate one in.
    bsf        STATUS,C
    rrf        dataPacket,F
    rrf        addressPacket,F
    ; if Carry is set, we've acquired 16 bits and we are done.
    btfsc    STATUS,C
        goto    getBitLoop
waitForStopBit
    ; signal is low.  Wait for high
    btfss    irPortBit
        goto    waitForStopBit
    ; Now we can send our packets


  Here is the full code: Read IR Test Code

  In the init of getIrData, we put a one in the eighth bit of the addressPacket.  The data is coming to us address, then data, and each will come least significant bit first.  So, we will rotate each data bit we receive through the data packet, then the address packet.  When we use the rrf rotate function, it takes what is in the Carry bit of the STATUS register and pushes that on the eighth bit of the dataPacket.  If the bit received is a one, the code puts a one in Carry and shifts it onto the eighth bit of dataPacket.  This shifts every bit to the right.  The one that is placed on the eighth
bit of dataPacket will eventually pop out and go into the Carry ( rotate is like a loop of the register and the Carry flag ).  The code can check the Carry bit for a one to see if all 16 bits have been received. 

   The testIrHighTime routine serves two very important functions.  First, it waits 1.5T ( ~700 us ).  This places the code about .5T past where a zero signal would end.  If the signal has gone low already, the code knows we received a zero.  If it is still high, the code waits an additional 2T.  This places it .5T past where a one would end.  If it has gone low, the code processes a one.  If it is still high, then one of two things has happened.  Either a start header is being sent, or the signal is in the high time of a stop bit when the code was not expecting it.  In either case, we simply return.  The
next low signal will pop in again.  Eventually, it will align at the start of a signal and read the packets.

Here is a picture of a simple test rig.  Note, the numbers being displayed are wrong.  I was still fiddling with the routine when I took the pictures. 





Here are the codes for my stereo remote:

Address: 10011111  ( Decimal 159, Hex 0x9F )



Value        Code        Decimal        Hex
 1        00100001      33              0x21
 2        00100010      34              0x22
 3        00100011      35              0x23   
 4        00100100      36              0x24
 5        00100101      37              0x25
 6        00100110      38              0x26
 7        00100111      39              0x27
 8        00101000      40              0x28
 9        00101001      41              0x29
10       00101010      42              0x2A
10+    00101110      46              0x2E

Disk:
1        11110001     241        0xF1
2        11110010     242        0xF2
3        11110011     243        0xF3
4        11110100     244        0xF4
5        11110101     245        0xF5
6        11110110     246        0xF6

Power        00000000       0        0x00
Volume Up    00000001       1        0x01
Volume Down    00000010       2        0x02

EDIT
    On Feb 3rd, 2012, I received an inexpensive logic analyzer that I had ordered.  It is the Open Workbench Logic Sniffer.  I was able to capture the signals coming in from the IR remote.  Here is a shot of the header, address, command for the "10" key:



I'm pretty stoked to have this little guy even if the memory is small.

No comments:

Post a Comment