Saturday, July 7, 2012

IR TV Remote Sensing with a PIC processor


Hacking around with a PIC18F1320 to do some IR sensing and, eventually, generation.

Working with a Sylvania TV remote.
Remote control with appropriate foreshortening for creepy effect

IR Reception

This remote seems to use IR (~900 nm?) with a carrier around 38.5KHz and PWM encoding.

I'm using a basic RadioShack IR receiver:

Pinout from front view:

+-----+
| /-\ |
| \-/ |
+-----+
 | | |
 | | |
 | | |
 | | +-- Vcc
 | +---- Gnd
 +------ Signal


Signal is normally 1; it goes LOW when an IR signal is detected. This is connected to a PIC via pin 18 (RB3).

IR Interpretation and Display

With this setup, I can read RB3 to see if the IR receiver is detecting a pulse. By counting how long pulses are present or absent, I can determine the encoding of each signal. Packets are received, one bit at a time, and stored in a series of four bytes.

After the STOP bit is received, I send the received bit pattern to my laptop via a serial port connection. The PIC's internal UART makes this transmission simple (code included below). I send the UART output through a 1K/2.2K ohm voltage divider to scale high outputs to ~3.3V (probably not necessary). This signal connects to a CP2103 breakout board (http://http://www.sparkfun.com/products/199): an easy way to connect uPs to a host machine via USB :) For this setup I only need to connect to 2 pins on the breakout board:

RXI this is where data is sent; and
GND

Then I can read transmitted bytes using minicom.

Full setup. That's a PICKit2 programmer on the right. IR receiver is next to the red wire on the left.

IR Encoding

The remote seems to use a standard NEC format:
ON for 8.6, OFF for 4.3 - START BIT
On for 0.6, OFF for either 2.4 (1) or 0.6 (0) - REPEAT FOR EACH BIT
ON for 0.6, OFF for >10 - STOP bit

Note that the information is encoded in the duration of the OFF pulses. This may be different from SONY encoding?

NOTE that repeat codes are different...

I then hit different buttons on the remote to see what was being transmitted. Here's the results.

RESULTS

Each packet appears to be 32 bits.
Some of the capture codes. I changed from showing all 4 bytes to just the final byte :)
Written as LSB first, the first byte received is 0x7B, followed by 0x1F These seem to be some sort of device code.
The third byte is the complement of the actual data byte (4th byte).
Thus there seems to be a simple 1-byte encoding per key. The mapping is as follows:

Final Code Table

BUTTON CODE(hex)
-----------------
POWER 20
0     00
1 01
2 02
3 03
4       04
5       05
6       06
7 07
8       08
9       09
Prev Ch 57
. 1B
Source  40
Sleep 22
Freeze  9E
Format 90
Eco 26
Menu 70
UP 73
DOWN 74
RIGHT 75
LEFT 76
OK    77
Back  78
Info  9B
Vol+  60
VOL-  61
SAP 65
Mute    64
CH+ 50
CH- 51

So the VOL+ button, for example, sends 7B 1F 9F 60 (first byte's LSB first).

Next Steps

The next step is to use this information to generate IR pulses to control the TV via the PIC uP. But since I don't actually watch that much TV, it's not clear why I would want to do this! Except I can combine this with the WiFly board I've been playing with and be able to control my TV from down the street :P

IR DETECTION CODE:

;
; Try receiving IR codes from a TV remote
; send the info out a serial port to host PC

; N. Macias  July 2012
; Trying to use the internal UART capability vs. bit-banging
; Also want to figure out the receive-interrupt options
;
    #include    "P18F1320.INC"
    list    p=18F1320
    config  wdt=off, lvp=off
    radix   dec
    EXTERN  serialInit,serialXmit,sendBCD,sendHexDigit

    udata
byte0       res 1   ; LSBs
byte1       res 1   ; could do an array, but this is easier!
byte2       res 1
byte3       res 1   ; MSBs

bitMask     res 1
bitCount    res 1   ; current bit# to receive
pulseWidth  res 1   ; # of 100 uS intervals during pulse
offTime     res 1   ; saved OFF-time duration during bit ingest
    code
    org     0

Start:
    call    serialInit
    bsf     TRISB,3     ; RB3 is an input: connected to 38.5KHz IR detector
                        ; outputs 0 on active pulse
mainLoop:
    call    receivePacket   ; get a single IR packet: byte0<LSB> is first bit
; Looks like we always receive 32 bits: byte0=0x7B, byte1=1F (xmit LSB first)
; Then we get a 2 digit code for the key we've pressed
; final byte is the actual code
; previous bye is compliment of final byte.
; So our code for key '1" is 01, which comes out as 7B 1F FE 01
; So let's just look at the final byte of the code :)
    movf    bitCount,w
    ;call    sendBCD
    movf    byte0,w
    ;call    sendBCD
    movf    byte1,w
    ;call    sendBCD
    movf    byte2,w
    ;call    sendBCD
    movf    byte3,w
    call    sendBCD
    movlw   32
    call    serialXmit
    goto    mainLoop

; receive a single packet, save in byte1 and byte2
; NEC coding:
;   8.55 ON, 4.275 OFF - START
;   0.55 ON, either 2.4 OFF (1) or 0.6 OFF (0) - repeat for each bit
;   0.55 ON, 35 OFF - STOP

receivePacket:
    clrf    bitCount    ; # bits received
    clrf    byte0
    clrf    byte1
    clrf    byte2
    clrf    byte3
    movlw   1
    movwf   bitMask     ; store first bit in LSB
startLoop:  ; start pulse should be 8.55 mS
    call    rcvON
    sublw   80              ; 80-WREG. wanted more than 80 pulses
    bnn     receivePacket   ; 80 or fewer - go back and try again
    call    rcvOFF
    sublw   35
    bnn     receivePacket   ; OFF time was <=3.5 mS...seems bogus, try again

; we got a good start bit here :) Now sample each bit, and save a 1 or 0
; based on the OFF time
bitLoop:
    call    rcvON       ; look for 0.55 ON time
    sublw   3           ; Expect > 0.3mS
    bnn     receivePacket   ; something went wrong! Restart
    call    rcvOFF      ; check length of OFF pulse
    movwf   offTime     ; save this
    movlw   50          ; If more than 5 mS, assume end of packet
    cpfslt  offTime
    return              ; STOP bit: all done!
    movlw   12          ; if < 1.2mS, this is a 0
    cpfslt  offTime
    goto    bitIs0      ; Don't need to do anything to store a 0
    movf    bitMask,w   ; mask to OR with
    iorwf   byte3       ; set this bit
bitIs0:
    rlncf   bitMask     ; ready to store in next position

    incf    bitCount    ; # of bits we've stored
    movlw   32          ; exit after 32 bits max
    cpfslt  bitCount
    return

    movlw   7           ; let's see if we hit a boundary
    andwf   bitCount,w  ; keep 3 LSBs
    bnz     bitLoop     ; nope...just keep going!
; We filled byte 3
    movff   byte1,byte0
    movff   byte2,byte1
    movff   byte3,byte2
    clrf    byte3
    movlw   1
    movwf   bitMask     ; mask got zeroed out!
    goto    bitLoop

;
; rcvON - wait for an input pulse to appear (RB3=0); then start counting
; until it ends (RB3=1)
; Return time / 100uS in W
rcvON:
    clrf    pulseWidth
rcvONLoop1:             ; wait for input to turn on
    btfsc   PORTB,3     ; skip if bit=0
    goto    rcvONLoop1  ; else go back and wait some more

; we've seen the start of a pulse...see how long it lasts
rcvONLoop2:
    incf    pulseWidth  ; # of 100 uS intervals (init=1)
    call    delay100
    btfss   PORTB,3     ; skip next if pulse has ended
    goto    rcvONLoop2
; pulse has ended
    movf    pulseWidth,w ; return pulsewidth
    return
;
; rcvOFF - pulse is already off (RB3=1)
; measure time until pulse appears (RB3=0)
; but time-out after 10 mS and just return 10 mS
; Return time / 100uS in W
rcvOFF:
    clrf    pulseWidth
rcvOFFLoop:
    movlw   100
    cpfslt  pulseWidth  ; Skip next if <10mS so far
    return              ; otherwise return NOTE W=100
    incf    pulseWidth  ; # of 100 uS intervals (init=1)
    call    delay100
    btfsc   PORTB,3     ; skip next we see the start of a pulse
    goto    rcvOFFLoop
; new pulse is starting
    movf    pulseWidth,w ; return pulsewidth
    return

; delay 100 uS. Based on 8MHz FOSC
; that's 200 instruction cycles
; will use 4 for the call and return, 1 for the movlw
delay100:
    movlw   65  ; 65*3+5=200 :)
delay100Loop:
    decfsz  WREG
    goto    delay100Loop
    return

    end

UART COMMUNICATION CODE:


;

; Serial (UART) communication code for PIC 18F1220

; N. Macias  July 2012

; Trying to use the internal UART capability vs. bit-banging

; Also want to figure out the receive-interrupt options

;

    #include    "P18F1320.INC"

    list    p=18F1320
    radix   dec
    GLOBAL serialInit,serialXmit,sendBCD,sendHexDigit

    udata
charToSend  res 1   ; temp storage
saveBCDIn   res 1   ; for sendBCD
tempDigit   res 1   ; for sendHexDigit
    code


serialInit:
    movlw   0x70
    iorwf   OSCCON     ; Set bits <6:4> for 8MHz operation
    bsf     ADCON1,5    ; TX on RB1 (pin 9); RX on RB4 (pin 10)
    bsf     ADCON1,6    ; set these as digital lines
    bsf     TRISB,1
    bsf     TRISB,4     ; initially inputs; UART module will adjust as needed

    movlw   0x24
    movwf   TXSTA   ; 8-bit; TX enabled; async; high speed (BRGH)
    movlw   0x90
    movwf   RCSTA   ; Enable serial port :) RX enabled
    movlw   0x08    ; BRG16
; baud rate generation
    movlw   0x08
    movwf   BAUDCTL ; 16-bit baud rate register (BRG16)
    movlw   207     ; =9600 baud with 8MHz FOSC
    movwf   SPBRG   ; 16 for 115.2K :)
    clrf    SPBRGH

    return

; send a single character - no handshake etc.
serialXmit:
    movwf   charToSend      ;save this while we check blocking etc.
    call    blockTillXmitReady
    movf    charToSend,w
    movwf   TXREG
    return

; wait until we're clear to transmit
blockTillXmitReady:
    btfss   PIR1,4      ; SET when ready to send
    goto    blockTillXmitReady
    return

; take value in W, convert to two BCD digits and send out serial line
sendBCD:
    movwf   saveBCDIn
    swapf   saveBCDIn
    movlw   0x0f
    andwf   saveBCDIn,w ; MSDigit
    call    sendHexDigit    ; Send that single digit
    swapf   saveBCDIn
    movlw   0x0f
    andwf   saveBCDIn,w
    call    sendHexDigit
    return

; Send a single hex digit
sendHexDigit:
    movwf   tempDigit
    movlw   10
    cpfslt  tempDigit   ; skip if digit<10
    goto    G10         ; else W is between 10 and 15
; simple digit here
    movlw   0x30        ; '0'
    addwf   tempDigit,w ; w has our digit
    call    serialXmit
    return

G10: ; num >= 10
    movlw   55
    addwf   tempDigit,w ; 10->65, etc
    call    serialXmit
    return

    end

No comments:

Post a Comment