NEC protocol - Remote control pulses

LOL... Here shown on the top is the output captured directly from the hand control; on the bottom is my reconstruction using a PIC microcontroller. Identical, yes? Herein lies the point of this fun exercise, the plucking of signal intelligence from the air and reproducing it, obviating the use of the original cumbersome equipment. The hand control is usually required to do something as simple as cycling power, but why should something as big as a banana be required to do something the size of a pomegranate seed could do? A pulse from a microcontroller could be easily persuaded, through TCP/IP, to prompt the playback of this simple routine from from anywhere on Earth.
The pulse train from the PIC modulates (turns on and off in this case) a continuous buzz of 38KHz that is picked up through the air by the IR receiver IC, which strips the 38KHz carrier, and presents the data shown above. You don't see anything as fast as 38KHz in the picture above because it's been removed already.
Skip the wide pulses to get to the data: the wide space at the beginning is simply a space after the initial AGC pulse, only a part of which is shown. Zeros are pulses with little space after them; Ones are identical pulses with a wider space. These are negative-going pulses because the IR receiver produces inverted output.
So the code you're looking at above reads:
11001100 00000000
That's the address for the unit to be controlled. Next comes the command. For instance, the command to turn the device on or off is:
00000000
11111111
Eight bits, then those eight bits inverted. Here's the command for "menu":
00111000
11000111
The next couple of buttons followed the pattern: start pulse - start pause - address - command. Hence, the remote was half-cracked when I had a list of all the commands.
Once I had a command table, the innards of this particular command set were laid open. What could I do with this information? Had I read the manual, I would have discovered that this fine unit was programmable, and I could come home to a air-conditioned apartment by pressing the button seven times before I left in the morning. This seemed to me something a robot could do, and I work even today toward other arrangements.
Hamilton Bay Air Conditioner IR codes:
address: 10000001 01100110
On/Off:
10000001
01111110
Mode:
11011001
00100110
Fan Speed:
10011001
01100110
Timer:
11111001
00000110
Temp Up:
10100001
01011110
Temp Down:
01010001
10101110
SANYO CXVM IR CODES:
address: 11001100 00000000
On/Off:
00000000
11111111
Computer:
00011100
11100011
Video:
10100000
01011111
Menu:
00111000
11000111
Up:
00110001
11001110
Right:
10111000
01000111
Left:
01111000
10000111
Down:
10110001
01001110
Select:
11110000
00001111
I'm not saying my programming is especially good, and there's really no warranty on any of this stuff. If anyone knows a more concise way to do what I'm doing, I'm always interested in learning something new. I'm sort of weak on DATA statements, so this is all kludgy brute-force stuff I ended up doing. In the program below, I just kept hacking at the problem until things worked. Here's the PicBasic Pro code for the Sanyo hand control. It even has all the tasty code for the other buttons I never ended up using.
' This program clones a SANYO CXVM. "Final Extra Pulse is crucial!
'_DEVICE 16f877A
' New version goes with the 3-button box with Allen-Bradley lighted
' switches.
OPTION_REG.7=0 ' enable port b pullups
ADCON1=0
CMCON=0
L var byte ' count for lamp flash
L1 var PORTB.1 ' I mixed up the switches. 1 is first, 0 second. sorry.
L2 VAR PORTB.0
L3 VAR PORTB.2
P VAR PORTB.3 ' This is a pulse
N VAR PORTB.4 ' This is a null, that has the same timing as a pulse, on a junk pin
zero con 56 ' The binary ZERO
zp con 44 ' The ZERO's pause
one con 56 ' The binary ONE
op con 158 ' The ONE's pause
ct var byte ' container for a count, so that repetitions of a ZERO or ONE can be done
TRISB=%11100000 ' Three input buttons, rest outputs on PORTB
low PORTB.0
LOW PORTB.1
LOW PORTB.2
LOW PORTB.3
LOW PORTB.4
Start:
if (PORTB.5 = 1) and (PORTB.6 = 1) and (PORTB.7 = 1) THEN start
If PORTB.5 = 0 then Oflight
if PORTB.6 = 0 then OLight
if PORTB.7 = 0 then Llight
goto start
OfLight:
Gosub onoff
pause 100
gosub onoff
for L = 1 to 10
high L2
pause 800
low L2
pause 800
next L
high L2
pause 10000
low L2
goto start
Olight:
'high l1 THESE COMMENTED OUT !!! ERROR, too much current
'pause 100 draw from lamp and failure of IR tx. Found!
gosub OnOff
for L=1 to 10
high L1
pause 800
LOW L1
pause 800
next L
high L1
pause 10000
LOW L1
goto start
llight:
high PORTB.2
pause 1000
low PORTB.2
goto start
OnOff:
gosub preamble
for ct = 1 to 8
gosub sendzero
next
for ct = 1 to 8
gosub sendone
next
gosub sendone 'final extra pulse
return
computer:
gosub preamble
for ct=1 to 3
gosub sendzero
next
for ct = 1 to 3
gosub sendone
next
gosub sendzero
gosub sendzero
for ct = 1 to 3
gosub sendone
next
for ct = 1 to 3
gosub sendzero
next
gosub sendone
gosub sendone
gosub sendone 'final extra pulse
pause 300
pause 500
goto start
video:
gosub preamble
gosub sendone
gosub sendzero
gosub sendone
for ct = 1 to 6
gosub sendzero
next
gosub sendone
gosub sendzero
for ct = 1 to 5
gosub sendone
next
gosub sendone 'final extra pulse
pause 300
goto start
menu:
'hserout [" menu", 13,10]
gosub preamble
gosub sendzero
gosub sendzero
for ct = 1 to 3
gosub sendone
next
for ct = 1 to 3
gosub sendzero
next
gosub sendone
gosub sendone
for ct = 1 to 3
gosub sendzero
next
for ct = 1 to 3
gosub sendone
next
gosub sendone 'final extra pulse
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
up:
'hserout [" up arrow", 13,10]
gosub preamble
gosub sendzero
gosub sendzero
gosub sendone
gosub sendone
for ct=1 to 3
gosub sendzero
next
gosub sendone
gosub sendone
gosub sendone
gosub sendzero
gosub sendzero
for ct = 1 to 3
gosub sendone
next
gosub sendzero
gosub sendone 'final extra pulse
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
rt:
'hserout [" right arrow", 13,10]
gosub preamble
gosub sendone
gosub sendzero
for ct = 1 to 3
gosub sendone
next
for ct = 1 to 4
gosub sendzero
next
gosub sendone
for ct = 1 to 3
gosub sendzero
next
for ct = 1 to 3
gosub sendone
next
gosub sendone 'final extra pulse
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
lft:
'hserout [" left arrow", 13,10]
gosub preamble
gosub sendzero
for ct = 1 to 4
gosub sendone
next
for ct = 1 to 3
gosub sendzero
next
gosub sendone
for ct = 1 to 4
gosub sendzero
next
for ct = 1 to 3
gosub sendone
next
gosub sendone
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
dn:
'hserout [" down arrow", 13,10]
gosub preamble
gosub sendone
gosub sendzero
gosub sendone
gosub sendone
for ct = 1 to 3
gosub sendzero
next
gosub sendone
gosub sendzero
gosub sendone
gosub sendzero
gosub sendzero
for ct = 1 to 3
gosub sendone
next
gosub sendzero
gosub sendone 'final extra pulse
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
sel:
'hserout [" select", 13,10]
gosub preamble
for ct = 1 to 4
gosub sendone
next
for ct= 1 to 8
gosub sendzero
next
for ct=1 to 4
gosub sendone
next
gosub sendone 'final extra pulse
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
preamble:
pulsout p, 900
pulsout n, 450
gosub sendone
gosub sendone
gosub sendzero
gosub sendzero
gosub sendone
gosub sendone
gosub sendzero
gosub sendzero
for ct = 1 to 8
gosub sendzero
next
return
sendzero:
pulsout p, zero
pulsout n, zp
return
sendone:
pulsout p, one
pulsout n, op
return
'_DEVICE 16f877A
' New version goes with the 3-button box with Allen-Bradley lighted
' switches.
OPTION_REG.7=0 ' enable port b pullups
ADCON1=0
CMCON=0
L var byte ' count for lamp flash
L1 var PORTB.1 ' I mixed up the switches. 1 is first, 0 second. sorry.
L2 VAR PORTB.0
L3 VAR PORTB.2
P VAR PORTB.3 ' This is a pulse
N VAR PORTB.4 ' This is a null, that has the same timing as a pulse, on a junk pin
zero con 56 ' The binary ZERO
zp con 44 ' The ZERO's pause
one con 56 ' The binary ONE
op con 158 ' The ONE's pause
ct var byte ' container for a count, so that repetitions of a ZERO or ONE can be done
TRISB=%11100000 ' Three input buttons, rest outputs on PORTB
low PORTB.0
LOW PORTB.1
LOW PORTB.2
LOW PORTB.3
LOW PORTB.4
Start:
if (PORTB.5 = 1) and (PORTB.6 = 1) and (PORTB.7 = 1) THEN start
If PORTB.5 = 0 then Oflight
if PORTB.6 = 0 then OLight
if PORTB.7 = 0 then Llight
goto start
OfLight:
Gosub onoff
pause 100
gosub onoff
for L = 1 to 10
high L2
pause 800
low L2
pause 800
next L
high L2
pause 10000
low L2
goto start
Olight:
'high l1 THESE COMMENTED OUT !!! ERROR, too much current
'pause 100 draw from lamp and failure of IR tx. Found!
gosub OnOff
for L=1 to 10
high L1
pause 800
LOW L1
pause 800
next L
high L1
pause 10000
LOW L1
goto start
llight:
high PORTB.2
pause 1000
low PORTB.2
goto start
OnOff:
gosub preamble
for ct = 1 to 8
gosub sendzero
next
for ct = 1 to 8
gosub sendone
next
gosub sendone 'final extra pulse
return
computer:
gosub preamble
for ct=1 to 3
gosub sendzero
next
for ct = 1 to 3
gosub sendone
next
gosub sendzero
gosub sendzero
for ct = 1 to 3
gosub sendone
next
for ct = 1 to 3
gosub sendzero
next
gosub sendone
gosub sendone
gosub sendone 'final extra pulse
pause 300
pause 500
goto start
video:
gosub preamble
gosub sendone
gosub sendzero
gosub sendone
for ct = 1 to 6
gosub sendzero
next
gosub sendone
gosub sendzero
for ct = 1 to 5
gosub sendone
next
gosub sendone 'final extra pulse
pause 300
goto start
menu:
'hserout [" menu", 13,10]
gosub preamble
gosub sendzero
gosub sendzero
for ct = 1 to 3
gosub sendone
next
for ct = 1 to 3
gosub sendzero
next
gosub sendone
gosub sendone
for ct = 1 to 3
gosub sendzero
next
for ct = 1 to 3
gosub sendone
next
gosub sendone 'final extra pulse
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
up:
'hserout [" up arrow", 13,10]
gosub preamble
gosub sendzero
gosub sendzero
gosub sendone
gosub sendone
for ct=1 to 3
gosub sendzero
next
gosub sendone
gosub sendone
gosub sendone
gosub sendzero
gosub sendzero
for ct = 1 to 3
gosub sendone
next
gosub sendzero
gosub sendone 'final extra pulse
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
rt:
'hserout [" right arrow", 13,10]
gosub preamble
gosub sendone
gosub sendzero
for ct = 1 to 3
gosub sendone
next
for ct = 1 to 4
gosub sendzero
next
gosub sendone
for ct = 1 to 3
gosub sendzero
next
for ct = 1 to 3
gosub sendone
next
gosub sendone 'final extra pulse
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
lft:
'hserout [" left arrow", 13,10]
gosub preamble
gosub sendzero
for ct = 1 to 4
gosub sendone
next
for ct = 1 to 3
gosub sendzero
next
gosub sendone
for ct = 1 to 4
gosub sendzero
next
for ct = 1 to 3
gosub sendone
next
gosub sendone
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
dn:
'hserout [" down arrow", 13,10]
gosub preamble
gosub sendone
gosub sendzero
gosub sendone
gosub sendone
for ct = 1 to 3
gosub sendzero
next
gosub sendone
gosub sendzero
gosub sendone
gosub sendzero
gosub sendzero
for ct = 1 to 3
gosub sendone
next
gosub sendzero
gosub sendone 'final extra pulse
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
sel:
'hserout [" select", 13,10]
gosub preamble
for ct = 1 to 4
gosub sendone
next
for ct= 1 to 8
gosub sendzero
next
for ct=1 to 4
gosub sendone
next
gosub sendone 'final extra pulse
pause 300
'high led
pause 500
'low led
'nput = "x"
goto start
preamble:
pulsout p, 900
pulsout n, 450
gosub sendone
gosub sendone
gosub sendzero
gosub sendzero
gosub sendone
gosub sendone
gosub sendzero
gosub sendzero
for ct = 1 to 8
gosub sendzero
next
return
sendzero:
pulsout p, zero
pulsout n, zp
return
sendone:
pulsout p, one
pulsout n, op
return
And here's the air conditioner hack, with even more ghastly programming.
'****************************************************************
'* Name : AirconCtrl.bas *
'* Author : Greg *
'* Notice : Copyright (c) 2008 Greg *
'* : All Rights Reserved *
'* Date : 5/18/2008 *
'* Version : 1.0 *
'* Notes : *
'* : *
'****************************************************************
' This program clones my Hampton Bay IR remote.
CMCON0 = 7
ANSEL = 0
TRISIO = %110001 'pins 2 and 3 are outputs. 3 is a null for pulsout
OPTION_REG.7 = 0 ' Global GPPU enabled
'WPU.0 = 1 ' weak pull-up enabled on GPIO.0
P VAR GPIO.2
N VAR GPIO.1
Counter var Byte
Number var byte
SPulse CON 900 'start pulse, 9ms
SPause CON 450 'start pause, 4.5ms
MPulse CON 60 'main pulse width, 600us
MPause CON 50 'main pause width, 500us
WPause CON 165 'medium-wide pause, 1.65ms
EPause CON 225 'end pause, 2.25ms
Low P
Start:
if (GPIO.0 = 1) and (GPIO.4 = 1) and (GPIO.5 = 1) THEN start
If GPIO.0 = 0 then Timer
if GPIO.4 = 0 then OnOff
if GPIO.5 = 0 then Mode
pause 300 ' little delay for switch
goto start
OnOff:
Gosub Init
Counter = 2
gosub Pulsecount
Counter = 7
Gosub Pulsecount
COunter = 2
Gosub Pulsecount
COunter = 1
Gosub Pulsecount
Counter = 1
Gosub Pulsecount
Counter = 1
Gosub Pulsecount
Counter = 1
Gosub Pulsecount
Counter = 1
Gosub Pulsecount
Counter = 2
Gosub Pulsecount
Gosub Endit
TempDown:
gosub init
counter = 3
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 4
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 2
gosub Pulsecount
gosub Endit
Return
TempUp:
gosub init
Counter = 2
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 5
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
COunter = 1
gosub Pulsecount
Counter = 1
gosub Pulsecount
COunter = 2
gosub Pulsecount
gosub Endit
Return
FanSpeed:
gosub Init
Counter = 2
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 1
gosub Pulsecount
COunter = 2
gosub Pulsecount
gosub Endit
return
Mode:
gosub Init
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 2
gosub Pulsecount
gosub Endit
return
Timer:
GOSUB Init
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 6
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 2
gosub Pulsecount
gosub Endit
return
Init:
Pulsout P, SPulse 'start pulse and pause
pulsout N, Spause
pulsout P, MPulse ' ONE
pulsout N, WPause ' seemingly universal preamble
Counter = 7
gosub Pulsecount
Counter = 2
Gosub Pulsecount
Counter = 1
Gosub Pulsecount
Counter = 3
Gosub Pulsecount
Counter = 1
gosub Pulsecount
return
Pulsecount:
Counter = Counter-1 'leave room for last pulse
For Number = 1 to Counter
pulsout P, MPulse
pulsout N, MPause
next
pulsout P, MPulse ' fire off that last pulse with wide back porch
pulsout N, WPause
return
Endit:
pause 42 ' ENDING
pulsout P, SPulse
Pulsout N, EPause
pulsout P, MPulse
return
'* Name : AirconCtrl.bas *
'* Author : Greg *
'* Notice : Copyright (c) 2008 Greg *
'* : All Rights Reserved *
'* Date : 5/18/2008 *
'* Version : 1.0 *
'* Notes : *
'* : *
'****************************************************************
' This program clones my Hampton Bay IR remote.
CMCON0 = 7
ANSEL = 0
TRISIO = %110001 'pins 2 and 3 are outputs. 3 is a null for pulsout
OPTION_REG.7 = 0 ' Global GPPU enabled
'WPU.0 = 1 ' weak pull-up enabled on GPIO.0
P VAR GPIO.2
N VAR GPIO.1
Counter var Byte
Number var byte
SPulse CON 900 'start pulse, 9ms
SPause CON 450 'start pause, 4.5ms
MPulse CON 60 'main pulse width, 600us
MPause CON 50 'main pause width, 500us
WPause CON 165 'medium-wide pause, 1.65ms
EPause CON 225 'end pause, 2.25ms
Low P
Start:
if (GPIO.0 = 1) and (GPIO.4 = 1) and (GPIO.5 = 1) THEN start
If GPIO.0 = 0 then Timer
if GPIO.4 = 0 then OnOff
if GPIO.5 = 0 then Mode
pause 300 ' little delay for switch
goto start
OnOff:
Gosub Init
Counter = 2
gosub Pulsecount
Counter = 7
Gosub Pulsecount
COunter = 2
Gosub Pulsecount
COunter = 1
Gosub Pulsecount
Counter = 1
Gosub Pulsecount
Counter = 1
Gosub Pulsecount
Counter = 1
Gosub Pulsecount
Counter = 1
Gosub Pulsecount
Counter = 2
Gosub Pulsecount
Gosub Endit
TempDown:
gosub init
counter = 3
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 4
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 2
gosub Pulsecount
gosub Endit
Return
TempUp:
gosub init
Counter = 2
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 5
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
COunter = 1
gosub Pulsecount
Counter = 1
gosub Pulsecount
COunter = 2
gosub Pulsecount
gosub Endit
Return
FanSpeed:
gosub Init
Counter = 2
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 1
gosub Pulsecount
COunter = 2
gosub Pulsecount
gosub Endit
return
Mode:
gosub Init
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 2
gosub Pulsecount
gosub Endit
return
Timer:
GOSUB Init
Counter = 2
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 3
gosub Pulsecount
Counter = 6
gosub Pulsecount
Counter = 1
gosub Pulsecount
Counter = 2
gosub Pulsecount
gosub Endit
return
Init:
Pulsout P, SPulse 'start pulse and pause
pulsout N, Spause
pulsout P, MPulse ' ONE
pulsout N, WPause ' seemingly universal preamble
Counter = 7
gosub Pulsecount
Counter = 2
Gosub Pulsecount
Counter = 1
Gosub Pulsecount
Counter = 3
Gosub Pulsecount
Counter = 1
gosub Pulsecount
return
Pulsecount:
Counter = Counter-1 'leave room for last pulse
For Number = 1 to Counter
pulsout P, MPulse
pulsout N, MPause
next
pulsout P, MPulse ' fire off that last pulse with wide back porch
pulsout N, WPause
return
Endit:
pause 42 ' ENDING
pulsout P, SPulse
Pulsout N, EPause
pulsout P, MPulse
return