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.

Saturday, December 17, 2011

Programming 16F Series Pics WIth pk2cmd

   Okay, this has happened before.  I solved it and forgot how I did it.  Lesson learned.  Here is the problem and solution.  I'm using a PicKit2 programmer with the open source pk2cmd software on Linux to program my Microchip PICs.  Most of the time, I have no problem.  Today, my 16F88 simple failed to do an erase and verify blank.  After a ton of Googling, I found a post that suggested that the -X switch may help.  This switch raises VPP before VDD.  I tried it, and it works!  Here are some samples:

pk2cmd -ppic16f88 -x -e
pk2cmd -ppic16f88 -x -c
pk2cmd -ppic16f88 -x -mcp -fmyCoolProg.hex

More later!

EDIT: So, I had the same troubles with my PIC18F2455 the other day.  This freaked me out a bit because I don't have the picp software for my PicStart Plus programmer anymore, and I don't think it can program my 18F2455s.  I tried the trick above, but it didn't work.  After a few deep breaths, I fiddled around a bit more.  The PicKit2 could identify the chip correctly when I used autodetect ( -p ).  I tried an erase and verify with autodetect, and it worked!  So, if you have trouble, first try autodetect.  If that doesn't work, try the voltage trick above.  If it does work, try using autodetect:
 pk2cmd -p -e
 pk2cmd -p -c
 pk2cmd -p -mcp -fmyCoolProg.hex

Once this has worked, the PicKit2 was able to take a chip reference again with no problem.  I was programming 2 different chips ( 16F88 and 18F2455 ) back and forth rather rapidly.  I think the PicKit2 just got confused or failed to reinitialize after each programming effort.

Saturday, December 3, 2011

Pulse Width Modulation (PWM) Calculations for PIC Micros

   Pic micros are really awesome little chips, and Microchip does a really good job documenting them.  Usually.  Sometimes, though, their docs assume something that I just completely fail to know about.  One such area was using their built in PWM on the CCP modules.  I could get them working.  I just cheated and used this Pic PWM Calculator.  But, reading their data sheets, I could never understand how the values were computed.  Tonight, I finally took some time to get it.  Here are the fruits of my labors.

   First, a review.  PWM wants to set up a period and a duty cycle.  The period is the length of time of one pulse cycle.  The duty cycle is the percentage of time in the period that the pulse will be high.  For Pic micros, the period is set with register PR2.  The duty cycle is a bit more complex.  It is shared between a register, such as CCPRnL and two bits in a control register such as CCPCONn<5:4>.  The lease significant bits are stored in the CCPCON<5:4> bits.  I'll talk about the simple Pic16F88.  It only has one CCP module, so the CCP register is CCPR1L and the control register is CCPCON. 

   First, the period must be set in register PR2.  For this example, we will set a 38 kHz period for a Pic16F88, running at 8 MHz.  The data sheet says that:

        PWM Period = [(PR2) + 1] • 4 • TOSC • (TMR2 Prescale Value)

A bit of algebra turns that into:

       PR2 = ( PWM Period / ( TOsc x 4 x TMR2 Prescaler ) ) - 1

Definitions:
       PWM Period = 1 / PWM frequency
       TOsc = 1 / MCU clock frequency

So, this looks like this for our 8 MHz Pic for a 38 kHz PWM:

       PR2 = ( ( 1/pulse frequency in Hz ) / ( 1/clock speed x 4 x prescaler value ) ) - 1
       PR2 = ( ( 1/38,000 ) / ( 1/8,000,000 ) x 4 x 1 ) ) - 1
       PR2 = ( ( .0000263 ) / ( .000000125 ) x 4 x 1 ) - 1
       PR2 =  52.6 - 1
       PR2 =  51.6 ( use 52 )

So, PR2 will get the value decimal 52 ( 0x34 ).  The prescaler worked with a simple x1 modifier, so TM2Con will be 0x04 ( 0b00000100 ).  If the value had been greater than 255, the TMR2 prescaler allows values of 1, 4, and 16.  Plug each value into the equation to get a PR2 value between 1 and 255.  If the value is still out of range, the PWM module cannot provide that frequency.  This is especially true of low frequencies needed for servo control.  It is fairly easy to implement those in software.  I've written code that controls 16 servos in software with interrupts.  I'll post that at some point.

    Now, for the duty cycle.  This is the part that drove me nuts.  The data sheet states that the duty cycle is:

    PWM Duty Cycle = (CCPR1L:CCP1CON<5:4>) • TOSC • (TMR2 Prescale Value)

   I couldn't for the life of me get this to translate into sensible values.  I used the PWM calculator to reverse engineer the equation.  Here are the definitions I came up with.

    Duty Cycle = ( 1/ pwm freq ) x percentage
    TOsc = 1 / clock freq.

    So, if I have a FOsc of 8 MHz and want 38 kHz at 50% duty cycle:

    Duty Cycle = ( 1 / 38,000 ) x .50 = .0000132
    TOsc = .000000125
    (CCPR1L:CCP1CON<5:4>) = .0000132 / .000000125 = 105.6
    use 106, in binary = 0b1101010
    So CCPR1L gets 0b11010 and CCP1CON<5:4> gets the lsbs: 0b10

   No matter what, the least significant bits go into CCP1CON<5:4>.  The rest goes into CCPR1L.  Note that the value for duty cycle is only 7 bits long, so my resolution is 7 bits for 38 kHz at 8 MHz.  That's it!

EDIT:
   I've been looking this over again.  The Duty Cycle is the percentage of the period frequency.  So, 50% of 38KHz is:
1/38000*.5 = 1.3157e-5 or ~ 0.00001316
0.0000132 * 8e6 = 105.6

This seems easier to think about.  It's just 1/pwm freq. * percentageAsDecimal * FOsc / prescaler.