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; andGND
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 :) |
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 :PIR 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