ElectronicsNerd.com

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



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