Sunday, July 8, 2012

Sending IR Signals to Control my TV

Quick follow-up to my prior post. Tried generating some IR codes to control my TV. Used a Radio Shack High-Output Infrared LED. Decided to tap the PWM sub-system of the PIC. Slightly complicated setup, but pretty straightforward. Set PWM on RB3/PA1 (pin 18, again) at 38.5KHz. Wrote a series of routines for sending a packet; bytes; and bits. Tried sending VOL+/VOL- pairs, but no joy. Tried receiving the IR output with the IR receiver, nada. Found some silly errors in my PWM handling (needed to arm the timer to drive PWM generation). After this, I was able to detect my generated signal :) Forward progress...

But still no joy on controlling the TV. The online information I found about NEC encoding suggests the first two bytes are a device code/complemented device code. This doesn't match my 0x7B/0x1F pair, but for some reason I was suspicious of that anyway. So I modified my code to cycle through all device IDs, hoping if I hit the right one, I'd see the button effect on-screen. But still nothing. However, while running this code, the regular remote stopped working: I was seemingly overwhelming the TV receiver with my own transmission. This meant I was definitely generating the right frequency, and was transmitting signals that the TV receiver couldn't ignore (and maybe I'd reinvented the "Stop That!" remote jammer from years ago).

Looking at the online doc further, I noticed the 3rd byte is supposed to be the command code, and the 4th the complement of it. My fourth byte is listed in the table above, and feels right: button 1 is 0x01, 2 is 0x02, etc. Buy if those are the complimented codes...maybe I messed up in my interpretation of the remote's transmissions?

I found a pair of valid codes that were compliments of each other: MUTE (0x64) and INFO (0x9B). If I attempted to send either, and my bits were reversed, I'd still be sending a valid code.

Still no response from the TV. So I switched back to my original device ID (7B1F); still nothing. Finally, I complemented the codes I was sending, and...


Mute command received, TV muted

Mute command received again, TV un-muted

So looks like my previous analysis was complemented?

I double-checked my earlier code, and sure-enough, I used a skip-if-less-than when I should have used a skip-if-greater-than: my codes were in fact complimented!

Or, rather, my device id was complemented, and my interpretation of byte order for bytes 3 and 4 were reversed. So the correct sequence is:

0x84/ 0xE0 / Command Code/ ~Command Code

So the above table is still correct. But send the true code as the third byte, and the complemented one as the fourth.


It's a bit glitchy. Interestingly, codes that end in a 1 work better than those that end in a 0. There's a big clue somewhere in there: almost certainly my timing is a bit off, and I'm crossing bit boundaries. Too tired to think deeply on this. But the basic setup seems to work. I'm using a granularity of 100 uS, I might do better with a smaller interval. Even better would be to use timer functions to reduce the variability due to code-path timing differences.


Just on a hunch, I tweaked my basic bit timing for a "1" bit, from an off time of 2.4 mS to 2.2 mS. Voila, much happiness! Even codes now work lol. Still some oddness, especially with sending different codes in rapid succession. More evidence of timing skew. Okay really strange observation: I put a (clear) glass in front of the LED to stop it from transmitting, but instead it seemed to make the system work even better! Something optical or multi-path going on? I can alternate channel and volume commands and it seems to do what it's supposed to. Much weirdness! OKAY FINAL TWEAK: scaled back my 0.6 mS intervals to 0.5 mS. Yay! Seems to work darn near perfectly now. Good enough for me, good proof of concept :)


; Try sending IR codes to TV
; N. Macias  July 2012
    #include    "P18F1320.INC"
    list    p=18F1320
    config  wdt=off, lvp=off
    radix   dec
    EXTERN  delay

bitCount    res 1
ourByte     res 1
workingByte res 1
iterCount   res 1   ; for delay100 code

    org     0

    call    IRInit
    ;movlw   0x9e    ; freeze code
    ;call    IRSend
    ;call    delay5Sec
    ;goto    mainLoop

    movlw   0x51    ; Channel down
    call    IRSend
    movlw   0x60
    call    IRSend
    movlw   0x60    ; vol+
    call    IRSend
    movlw   0x61    ; vol-
    call    IRSend
    movlw   0x40    ; Source
    call    IRSend
    movlw   0x40    ; Source
    call    IRSend
    goto    mainLoop

; IR initialization code
    movlw   0x70
    movwf   OSCCON  ; 8MHz operation

; start with output pin set to input
    bsf     TRISB,3 ; use pin 18 (RB3) as output to drive the IR transmitter

; we want to use the PWM feature of the PIC (P1A). This requires some setup...
; At a frequency of 38.5 KHz, we need PR2=51. and TMR2 prescale=1 (FOSC=8MHz)
; For 50% duty cycle we need to load 104. into CCPR1L:CCP1CON<5:4>
; 104. is 0001 1010 00 (10-bit binary): so set CCPR1L=0x1a and CCP1CON<5:4>=00

; set PWM period (51.)
    movlw   51
    movwf   PR2 ; set PWM period

; set ECCP control register
    movlw   0x0c ; 00 (P1A modulated) 00 (LSBs of duty cycle) 1100 (active high)
    movwf   CCP1CON

; set rest of PWM duty cycle (104. since not mult by 4)
    movlw   0x1a
    movwf   CCPR1L  ; MSBs of duty cycle (2 LSBs set to 00 above)

    bcf     ECCPAS,7    ; clear any shutdown code

; TMR2 setup
    bcf     PIR1,1  ; clearTMR2 int flag
    bcf     T2CON,0
    bcf     T2CON,1 ; set prescale value=1
    bsf     T2CON,2 ; Turn on the timer!

; To turn on PWM output, we need to:
; - wait until PIR1<1> is set (and then clear it?); and
; - clear bit 3 of TRISB
; to turn OFF, just set TRISB<3>


; Send full packet containing code (in W) through IR
; 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
    movwf   ourByte ; save this
; send START pulse
    call    IROn    ; begin START pulse
    movlw   86
    call    delay100
    call    IROff
    movlw   43
    call    delay100

; send 4-byte message
    movlw   0x84        ; start of address
    call    sendIRByte
    movlw   0xE0        ; end of address
    call    sendIRByte
    movf    ourByte,w
    call    sendIRByte  ; actual code
    movf    ourByte,w
    comf    WREG        ; toggle bits
    call    sendIRByte  ; and send actual code

; send STOP bit
    call    IROn
    movlw   5
    call    delay100
    call    IROff
    movlw   125
    call    delay100
    movlw   125
    call    delay100
    movlw   107
    call    delay100    ; total 35.7 mSec
    movlw   127
    call    delay100    ; extra just for good measure


; pump out a single byte
    movwf   workingByte ; send this byte as 8 pulse patterns
    movlw   8
    movwf   bitCount    ; count down to 0

    call    IROn        ; turn on the beam
    movlw   5
    call    delay100    ; ON pulse; approx 5.5 mSec

    call    IROff       ; and turn off
    movlw   5
    call    delay100    ; .6 mS off time (this is a 0)
    movlw   16          ; prepare for additional 1.8 mS off-time
    btfsc   workingByte,0   ; skip next if bit=0
    call    delay100    ; else wait additional time

; now move to next byte
    rrncf   workingByte ; move next bit to LSB
    decfsz  bitCount
    goto    sibLoop     ; repeat 8 times

    return              ; all bits sent!

; turn on PWM output
    btfss   PIR1,1  ; wait for interrupt request from timer
    goto    IROn
    bcf     PIR1,1  ; clear the bit (do we need to do this?)
    bcf     ECCPAS,7    ; clear any shutdown code
    bcf     TRISB,3 ; set RB3 as an output
    bsf     T2CON,2 ; Turn on the timer!

; and turn off PWM output
    bsf     TRISB,3

; delays 100 uS * WREG
; Based on 8MHz FOSC, that's 200 instruction cycles
    movwf   iterCount
delay100Loop1:      ; start of single 100 uS delay loop
    movlw   65  ; actual time is (199*w) + 5
    decfsz  WREG
    goto    delay100Loop2   ; inner loop runs 201 cycles
    decfsz  iterCount
    goto    delay100Loop1   ; repeat the whole thing

    movlw   50  ; wait 5 seconds
    call    delay   ; wait 0.1 sec
    decfsz  WREG    ; repeat 50 times
    goto    delay5SecLoop


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:// 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

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.


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

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