;********************************************************************
;																	*
;	XTB Serial Powerline Interface									*
;																	*
;	This program is for the plug-in XTB TW523 emulator (XTB-523)	*
;																	*
;********************************************************************
;																	*
;	Filename:		XTB-523.asm										*
; 	Date:			07/02/19										*
;	File Version:	rev 1.07										*
;																	*
;	Author:			Jeff Volp										*
;	Company:		JV Digital Engineering 							*
;																	*
;	This code is Copyright JV Digital Engineering 07/02/19			*
;																	*
;	Version 1.01													*
;		No noise sample when receiving X10 to ignore 2mS XPCR pulse *
;	Version 1.02 (7/9/13)											*
;		Improve AGC response to random "X10 like" signal bursts		*
;	Version 1.02a  (7/29/13)										*
;		Delete LED flicker when storm counter decreases to zero		*
;		Transmit doublet at startup to prevent rcv error indication	*
;		Correctly checks for errors receiving an extended command	*
;	Version 1.03 (1/5/14)											*
;		Sampling of digital input extended for RCS thermostat		*
;	Version 1.04 (1/17/14)											*
;		Corrects problem repeating bright/dim sequences				*
;		AGC modified to raise threshold for errors or transients	*
;	Version 1.05 (4/02/14)											*
;		Corrects bug in start pattern error handler					*
;	Version 1.06 (1/20/15)											*
;		Corrects bug in "return all bits" mode option				*
;	Version 1.07 (6/30/19)											*
;		Latches ZCD on both edges like XTBR	(mod for SMD version)	*
;																	*
;********************************************************************

	list      p=16F1823			; list directive to define processor
	#include <p16F1823.inc>		; processor specific variable definitions

	errorlevel  -302			; suppress message 302 from list file


    __CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_ON & _PWRTE_ON & _MCLRE_OFF & _CP_ON & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
    __CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_ON & _BORV_25 & _LVP_OFF

; I/O PIN DEFINITIONS

ZCDIN	EQU		H'05'			; (RA5) Zero Crossing Detector - 50/60Hz reference input
TP120K	EQU		H'04'			; (RA4) 120KHz PWM 120KHz reference test point
ICSVPP	EQU		H'03'			; (RA3) Programmer Master Clear / Vpp Input
TPX10	EQU		H'02'			; (RA2) X10 Input test point
ICSCLK	EQU		H'01'			; (RA1) Programmer Clock In (also mode select)
LEDGRN	EQU		H'00'			; (RA0) Programmer Data In, LED: 1 green, 0 red, ("0" for mode select)
TW523I	EQU		H'05'			; (RC5) Digital Data In - 0 for opto-isolator LED ON (1 input)
TW523O	EQU		H'04'			; (RC4) Serial Data Out - 0 for opto-isolator ON (low output)
PWM120	EQU		H'03'			; (RC3) 120KHz PWM output - directly drives power stage
TPDIAG	EQU		H'02'			; (RC2) Diagnostic Test Point
X10BPF	EQU		H'01'			; (RC1)	120KHz X10 input from powerline bandpass amplifier
LEDRED	EQU		H'00'			; (RC0) LED: 1 red, 0 green

; VARIABLE DEFINITIONS

			UDATA		0x20

rambgn
startw		RES		1			; startup window for configuration reset (0 when over)
periodH		RES		1			; period measurement MSB
periodL		RES		1			; period measurement LSB
frqerr		RES		1			; 2's compliment frequency error (0 for no error)
cyccnt		RES		1			; received bit cycle count
tstcnt		RES		1			; duplicate copy of received bit cycle count for test output
rcvsr1		RES		1			; received data shift register byte 1 (LSB is last bit received)
rcvsr2		RES		1			; received data shift register byte 2
rcvsr3		RES		1			; received data shift register byte 3 (standard message)
rcvsr4		RES		1			; received data shift register byte 4
rcvsr5		RES		1			; received data shift register byte 5
rcvsr6		RES		1			; received data shift register byte 6
rcvsr7		RES		1			; received data shift register byte 7
rcvsr8		RES		1			; received data shift register byte 8 (extended messages)
onezer		RES		1			; net total of one versus zero bits (error if not 1-3)
rcvcnt		RES		1			; received bit count
onecnt		RES		1			; received "1" count
gapcnt		RES		1			; received gap length (255 max)
outcnt		RES		1			; digital output bit count
seccnt		RES		1			; second timer - counts 60 to 1 each second
x10cnt		RES		1			; X10 traffic density (+3 for command, -1 every second)
msglen		RES		1			; number of bits in message
ledcnt		RES		1			; 2 X number of LED flashes for indicator (LSB = LED status)
ledtim		RES		1			; countdown timer for LED flashing
rhouse		RES		1			; unpacked receive message house code (4-bit X10 code)
rkeycd		RES		1			; unpacked received message key code (5-bit X10 code)
oldhse		RES		1			; last house code received
oldkey		RES		1			; last key code received
modcnt		RES		1			; number of "523" mode command digits accepted
modtim		RES		1			; XTB mode command window timer
modsel		RES		1			; valid 1-16 mode select (in X10 code)
xcmdcd		RES		1			; command code used to assemble a transmit message
xhouse		RES		1			; house code used to assemble a transmit message
xkeycd		RES		1			; key code used to assemble a transmit message
xmtsr1		RES		1			; transmit data shift register byte 1
xmtsr2		RES		1			; transmit data shift register byte 2
xmtsr3		RES		1			; transmit data shift register byte 3 (output)
xmtcnt		RES		1			; transmitted bit count
agclvl		RES		1			; AGC level (8x the value used for the D/A)
agcend		RES		1			; end of AGC sample window
thdset		RES		1			; comparator threshold setting
temp0		RES		1			; Bank 0 general purpose register
temp1		RES		1			; Bank 0 general purpose register (for mode command)
ramend

			UDATA_SHR	0x70

tmp_wrk		RES		1			; variable used for context saving 
tmp_sts		RES		1			; variable used for context saving
nxttune		RES		1			; next setting for OSCTUNE
temp2		RES		1			; global general purpose register (for AGC loop)

stsflg		RES		1			; general status flags

rcverr		EQU		H'07'		; "1" indicates an error was detected in the data pattern
rcvbit		EQU		H'06'		; "1" indicates received bit was a logic "1" (22 bit delay from input)
inpbit		EQU		H'05'		; "1" indicates last received bit is a logic "1" (as received)
modjmp		EQU		H'04'		; "1" indicates the mode jumper is installed
xtdcmd		EQU		H'03'		; "1" indicates receiving an extended command
dimcmd		EQU		H'02'		; "1" indicates receiving a bright/dim command
linein		EQU		H'01'		; "1" indicates a powerline input is being received
dgioin		EQU		H'00'		; "1" indicates a digital input is being received
allsrc		EQU		b'00001111'	; used to check if any X10 source flags are set (including dim & extended)
clrsrc		EQU		b'11111100'	; used to clear all X10 source flags

xmtflg		RES		1			; transmitter status flags

shutdn		EQU		H'07'		; "1" indicates shutdown due to command storm
colisn		EQU		H'06'		; "1" indicates a collision was detected
outbit		EQU		H'05'		; "1" indicates next bit to be transmitted is a logic "1"
xmtmsg		EQU		H'04'		; "1" indicates transmitter is active (from 1st start bit to 6-bit gap)
rptmsg		EQU		H'03'		; "1" indicates a message is being repeated (also set for re-transmits)
xtbmsg		EQU		H'02'		; "1" indicates an internal message is being transmitted (inhibit repeat)
xmtcod		EQU		H'01'		; "1" indicates the keycode should be transmitted
xmtcmd		EQU		H'00'		; "1" indicates the command should be transmitted

modflg		RES		1			; mode flags (copied from EEPROM)
  variable	moddef=b'00001000'	; default to basic TW523 (single phase, no repeater, collision abort)

xmt3ph		EQU		H'07'		; 8 = 1 enables transmit on all 3 phases (digital, & repeat)
rptena		EQU		H'06'		; 7 = 1 enables repeater function
onerpt		EQU		H'05'		; 6 = 1 selects only one repeat for each command (no ping-pong)
colrpt		EQU		H'04'		; 5 = 1 enables auto re-transmit of repeat following collision
colabt		EQU		H'03'		; 4 = 1 selects abort transmission when collision is detected (default on)
pgmhse		EQU		H'02'		; 3 = 1 is used to program housecode after ALL OFF reset (5-2-3-3-ON)
xmtdly		EQU		H'01'		; 2 = 1 selects delayed transmit to reduce potential for "dimmer flicker"
allbit		EQU		H'00'		; 1 = 1 selects return all bits received (no error checking)

hsemod		RES		1			; housecode accepted for mode commands
  variable	hsedef=b'00001100'	; default P, changed code used for 5-2-3-3-ON after ALL OFF reset

hseulk		EQU		H'07'		; unlock flag set at ALL OFF reset to allow house code to be changed

EEMODE		equ		H'0010'		; location of mode flags in EEPROM
EEHSE		equ		EEMODE+2	; location of housecode in EEPROM

;			Error Indications

ledmod		equ		.9			; 5 LED flashes for a mode command timeout
ledcol		equ		.7			; 4	LED flashes for a collision on transmit
lederr		equ		.5			; 3 LED flashes for an error receiving an incoming X10 command

;**********************************************************************

RESET_VECTOR	ORG		0x0000	; processor reset vector

		goto    start			; go to beginning of program

INT_VECTOR		ORG		0x0004	; interrupt vector location

		goto	intrpt			; process interrupt routine


;		X10 -> HEXIDECIMAL CODE CONVERSION UTILITY  (locate at program page zero)

;		Convert X10 code to 0-F hexdecimal equivalent (offset by 1 count)

;		Enter with W = X10 code in 4 LSBs where "1" = 0000
;		Returns with W = hex value of code (X10 1-16 -> hex 0-F)

x10hex	clrf	PCLATH			; assume page zero
		andlw	b'00001111'		; make sure 4 MSBs are zero
		addwf	PCL,F			; adjust program counter LSB
		retlw	.12				; M or 13 = '0000'
		retlw	.4				; E or  5 = '0001'
		retlw	.2				; C or  3 = '0010'
		retlw	.10				; K or 11 = '0011'
		retlw	.14				; O or 15 = '0100'
		retlw	.6				; G or  7 = '0101'
		retlw	.0				; A or  1 = '0110'
		retlw	.8				; I or  9 = '0111'
		retlw	.13				; N or 14 = '1000'
		retlw	.5				; F or  6 = '1001'
		retlw	.3				; D or  4 = '1010'
		retlw	.11				; L or 12 = '1011'
		retlw	.15				; P or 16 = '1100'
		retlw	.7				; H or  8 = '1101'
		retlw	.1				; B or  2 = '1110'
		retlw	.9				; J or 10 = '1111'


;		EXPAND X10 CODE CONVERSION UTILITY  (locate at program page zero)

;		Convert 4-bit X10 code to 8-bit X10 transmission code

;		Enter with W = X10 code in 4 LSBs
;		Returns with W = 8-bit X10 equivalent (decimal 0-15 -> X10 1-16)

expx10	clrf	PCLATH			; assume page zero
		andlw	b'00001111'		; make sure 4 MSBs are zero
		addwf	PCL,F			; adjust program counter LSB
 		retlw	b'01010101'		; M or 13 = b'0000'
		retlw	b'01010110'		; E or  5 = b'0001'
		retlw	b'01011001'		; C or  3 = b'0010'
		retlw	b'01011010'		; K or 11 = b'0011'
		retlw	b'01100101'		; O or 15 = b'0100'
		retlw	b'01100110'		; G or  7 = b'0101'
		retlw	b'01101001'		; A or  1 = b'0110'
		retlw	b'01101010'		; I or  9 = b'0111'
		retlw	b'10010101'		; N or 14 = b'1000'
		retlw	b'10010110'		; F or  6 = b'1001'
		retlw	b'10011001'		; D or  4 = b'1010'
		retlw	b'10011010'		; L or 12 = b'1011'
		retlw	b'10100101'		; P or 16 = b'1100'
		retlw	b'10100110'		; H or  8 = b'1101'
		retlw	b'10101001'		; B or  2 = b'1110'
		retlw	b'10101010'		; J or 10 = b'1111'


;		INTERRUPT PROCESSING

;		Identify the source of the interrupt

intrpt	clrf	BSR				; make sure BANK0 is selected
		bsf		PORTC,TPDIAG	; turn diagnostic pulse on
		btfsc	PIR1,TMR1IF		; skip if not a Timer 1 overflow
		goto	timint			; handle timer interrupt if so
		banksel	IOCAF			; select BANK7 for interrupt on change
		btfss	IOCAF,IOCAP5	; skip if it was a zero crossing interrupt
		goto	unknwn			; unidentified interrupt if not

;		Calculate line frequency period by adding each half cycle

		bcf		IOCAF,IOCAF5	; clear interrupt flag
		clrf	BSR				; back to Bank0
		bcf		INTCON,IOCIE	; disable ZCD interrupt on change
		bcf		T1CON,TMR1ON	; stop Timer 1
		movfw	TMR1L			; get Timer 1 LSB
		addwf	periodL,F		; add to last half cycle LSB
		btfsc	STATUS,C		; skip if carry clear
		incf	periodH,F		; propagate carry into MSB
		movfw	TMR1H			; get Timer 1 MSB
		addwf	periodH,F		; add to last half cycle MSB
		movlw	.247			; preset Timer 1 to -1.125 mS (at 8.16MHz clk)
		movwf	TMR1H			; set high byte
		movlw	.10				; adjust for period 66.4 MSB counts
		movwf	TMR1L			; set low byte
		bsf		T1CON,TMR1ON	; restart Timer 1

;		Clear watchdog timer, lock the ZCD to the present state

		clrwdt					; clear watchdog timer
		movlw	.10				; for a 15uS delay 
		movwf	temp1
intdly	decfsz	temp1,F			; tick off one loop @ 1.5uS per loop
		goto	intdly			; delay 15uS for stable sample of ZCD input level

		bcf		PORTA,ZCDIN		; to set ZCD output low
		btfsc	PORTA,ZCDIN		; skip if falling edge of AC input
		bsf		PORTA,ZCDIN		; to set ZCD output high
		banksel	TRISA			; select BANK1 for port direction register
		bcf		TRISA,ZCDIN		; set ZCD as an output
		clrf	BSR				; back to BANK0

;		Turn off LED if not flashing & check timeout status

chkfls	movf	ledcnt,F		; check if LED is being flashed
		btfss	STATUS,Z		; skip and check timeout enable if LED not flashing
		goto	xmtnxt			; output next bit when transmitting
		bcf		PORTA,LEDGRN	; turn off green LED and set mode jumper to "0"
		btfsc	PORTA,ICSCLK	; skip if serial ICSCLK is jumpered to ISCDATA
		bsf		stsflg,modjmp	; set mode jumper flag
		btfss	PORTA,ICSCLK	; skip if serial ICSCLK is not jumpered to ISCDATA
		bcf		stsflg,modjmp	; clear mode jumper flag

;		Clear source flags if no message is being processed

clrflg	movf	onecnt,F		; have there been any "1" bits received?
		btfss	STATUS,Z		; skip and clear X10 source flags if not
		goto	xmtnxt			; transmit next bit as required
		movlw	clrsrc			; to mask all X10 source flags
		andwf	stsflg,F		; clear all X10 source status flags

;		Turn on transmitter as required & set LED to red or green as appropriate

xmtnxt	btfss	xmtflg,xtbmsg	; skip if transmitting an internal message
		goto	chkrpt			; check if repeating a message
		btfss	xmtflg,outbit	; skip & transmit if output bit is a "1"
		goto	setgrn			; turn on green LED when transmitting a "0
		goto	x10out			; transmit bit

chkrpt	btfss	xmtflg,rptmsg	; skip if message is being repeated
		goto	setgrn			; turn on green LED when not repeating
		btfsc	stsflg,xtdcmd	; skip if not an extended command
		goto	chkxtd			; check extended command
		btfss	stsflg,rcvbit	; skip & transmit if normal bit is a "1"
		goto	setgrn			; turn on green LED when transmitting a "0"
		goto	x10out			; turn on transmitter
chkxtd	btfss	rcvsr8,5		; skip & transmit if extended bit is a "1"
		goto	setgrn			; turn on green LED when transmitting a "0"

x10out	bsf		xmtflg,outbit	; make sure output bit is set to a "1"
		call	xmton			; turn on transmitter & turn red LED on
		goto	chkpol			; next

setgrn	movf	ledcnt,F		; check if LED is being flashed
		btfss	STATUS,Z		; skip and turn on green LED
		goto	chkpol			; leave LED status alone when flashing
		bsf		PORTA,LEDGRN	; turn on green LED
		bcf		PORTC,LEDRED

;		Determine which AC input zero crossing just passed

chkpol	btfsc	PORTA,ZCDIN		; skip if falling edge of AC input
		goto	adjclk			; adjust clock frequency on rising edge

;		Count 60Hz to check noise & command storm once a second

		decfsz	seccnt,F		; tick off one cycle (delay 4 seconds after powerup)
		goto	strtup			; skip storm processing except at zero count
		movlw	.60				; reload count for next second
		movwf	seccnt

;		Check busy count to recover from a command storm

chktrf	movf	x10cnt,F		; is traffic count already zero?
		btfsc	STATUS,Z		; skip if not
		goto	strtup			; check startup window
		decfsz	x10cnt,F		; decrement traffic count & skip if went to zero
		goto	strtup			; check startup window
		btfss	xmtflg,shutdn	; skip if in command storm shutdown
		goto	intrtn			; return with interrupts enabled

		bcf		xmtflg,shutdn	; clear shutdown flag
		clrf	ledcnt			; stop LED flashing
		bcf		PORTC,LEDRED	; turn off red LED
		goto	intrtn			; return with interrupts enabled

;		Check startup window

strtup	movf	startw,F		; is startup window over?
		btfsc	STATUS,Z		; skip if not
		goto	chkmod			; check for mode command if so
		decfsz	startw,F		; decrement startup window & skip if just ended
		goto	intrtn			; return with interrupts enabled
		
;		Retrieve mode configuration from EEPROM at end of startup window

getmod
		banksel	EEADR			; select BANK3 to access EEPROM
		movlw	EEMODE			; get mode flags address in EEPROM
		movwf	EEADR			; set EEPROM address to read
		bsf		EECON1,RD		; read EEPROM location
		movfw	EEDAT			; get 1st set of mode flags
		movwf	modflg			; save in RAM for quick access
		incf	EEADR,F			; set next EEPROM address to read
		bsf		EECON1,RD		; read EEPROM location
		movfw	EEDAT			; get housecode used for mode programming
		movwf	hsemod			; save in RAM for quick access
modrtn	clrf	BSR				; make sure BANK0 is selected
		call	pwmpwr			; set PWM power level
		goto	intrtn			; return with interrupts enabled

;		Check mode command timeout on falling edge of AC input

chkmod	movf	modtim,F		; check mode command timer
		btfsc	STATUS,Z		; skip if not zero
		goto	intrtn			; just return if so
		decf	modtim,F		; tick off one count
		btfsc	STATUS,Z		; skip if not zero yet
		goto	clrmod			; clear mode select if timeout

;		Enable transmit mode select acknowledge when receive has completed

		btfss	xmtflg,xmtcmd	; skip if waiting to transmit a command word
		goto	intrtn			; just return if not

		movlw	.241			; allow a gap of 15 half cycles
		addwf	gapcnt,W		; is the line clear?
		btfss	STATUS,C		; skip if so
		goto	intrtn			; just return if not
		bsf		xmtflg,xtbmsg	; set flag to begin transmit acknowledge
		clrf	modtim			; clear mode command timer
		goto	intrtn			; return

;		Clear mode select digits if timeout

clrmod	clrf	modcnt			; clear mode command digit count
		clrf	modsel			; clear mode select
		clrf	rkeycd			; clear received key code
		movlw	ledmod			; number of flashes for a mode command timeout
		call	ledset			; set LED count for number of flashes
		goto	intrtn			; return with interrupts enabled if falling edge

;		Using line frequency as a reference, adjust clock to 8.16MHz on rising edge

adjclk	rlf		periodL,F		; double frequency resolution
		rlf		periodH,F
		btfsc	periodL,7		; round up MSB if LSB > 127
		incf	periodH,F		; MSB = 230 (60Hz) at 8.16MHz (with 247 Preset)
		movfw	periodH			; get period MSB
		addlw	.128			; adjust to center range
		clrf	periodL			; clear period measurement
		clrf	periodH
		addlw	.154			; calculate error at 60Hz: W = 0 at 8.16MHz
		btfsc	STATUS,Z		; skip if clock needs adjusting
		goto	ledfls			; check LED flashing if not
		addlw	.128			; add 128 to set C for negative error (freq too low)
		btfsc	STATUS,C		; skip if error is positive
		goto	adjpos			; increase frequency if negative error

adjneg	decf	frqerr,F		; decrease frequency error count
		btfss	frqerr,6		; adjust frequency if max count
		goto 	ledfls			; check LED flashing if not
		movfw	nxttune			; recall oscillator tune setting
		btfss	STATUS,Z		; no adjust if already at minimum frequency (0)
		decf	nxttune,F		; decrease frequency (write at next T1 interrupt)
		goto	rsterr			; preset error word to mid range

adjpos	incf	frqerr,F		; increase frequency error count
		btfss	frqerr,6		; adjust frequency if max count
		goto	ledfls			; check LED flashing if not
		incf	nxttune,F		; increase frequency (write at next T1 interrupt)
		movfw	nxttune			; recall oscillator tune setting
		addlw	.192			; check if beyond max (64 + 192 = 256)
		movlw	.63				; for maximum = 63
		btfsc	STATUS,C		; skip if within range (0 - 63)
		movwf	nxttune			; set to maximum if beyond maximum
rsterr	movlw	b'00100000'		; preset frequency error to mid-range
		movwf	frqerr

;		Control LED flashing to indicate an error condition

ledfls	movf	ledcnt,F		; check if LED is being flashed
		btfsc	STATUS,Z		; skip if so
		goto	intrtn			; return with interrupts enabled if not
		movlw	b'00011111'		; to prevent any flash rate longer than 1 sec
		andwf	ledtim,F		; check if countdown timer is still zero
		btfsc	STATUS,Z		; skip if not
		goto	ledflp			; start timer and set LED status	
		decfsz	ledtim,F		; tick off one count & skip if done
		goto	intrtn			; return with interrupts enabled if not

		movlw	b'10001000'		; for LED continuous flash mode
		btfsc	ledcnt,7		; skip if LED is not in continuous flash mode
		iorwf	ledcnt,F		; keep in continuous flash mode
		decfsz	ledcnt,F		; tick off one flash cycle & skip if done
		goto	ledflp			; change LED status if not
		goto	ledoff			; turn off LED if done
ledflp	movlw	.10				; 1/6 second (3 flashes per second)
		btfsc	ledcnt,7		; skip if not continuous flash mode (command storm)?
		movlw	.6				; 1/10 second (5 flashes per second continuous)
		movwf	ledtim			; start new time interval

		btfss	ledcnt,0		; skip if flash count LSB is one
ledoff	bcf		PORTC,LEDRED	; turn off red LED
		bsf		PORTA,LEDGRN
		btfsc	ledcnt,0		; skip if flash count LSB is zero
		bsf		PORTC,LEDRED	; turn on red LED
		bcf		PORTA,LEDGRN
		goto	intrtn			; return with interrupts

;		Turn off transmitter for single-phase output 1mS after zero crossing

timint	bcf		PIR1,TMR1IF		; clear Timer 1 interrupt flag
		call	xmtoff			; turn off transmitter

;		Update oscillator tune 1mS after zero crossing

oscadj	movfw	nxttune			; recall next oscillator tune setting
		xorlw	b'00100000'		; convert to offset binary for OSCTUNE
		banksel	OSCTUNE			; select Bank1
		movwf	OSCTUNE			; set next frequency
		clrf	BSR				; back to Bank0
		goto	intrtn			; return with interrupts enabled

;		Disable driver for unresolved interrupt

unknwn	clrf	BSR				; make sure BANK0 is selected
		call	xmtoff			; make sure transmitter is off

intrtn	clrf	BSR				; make sure BANK0 is selected
		bcf		PORTC,TPDIAG	; turn diagnostic pulse off
		retfie					; return with interrupts enabled

;		Set LED flash count if not already flashing (number of flashes in W)

ledset	movf	ledcnt,F		; check if LED is already being flashed
		btfsc	STATUS,Z		; skip if so (leave last count)
		movwf	ledcnt			; set LED flash count
		return


		; INITIALIZATION SUBROUTINE
		;
		; Note: Clock Oscillator starts at 16 MHz internal by default

initiz	clrf	BSR				; select Bank 0
		clrf	PORTA			; initialize any port A I/O bits to zero
		movlw	b'00000100'		; all port C I/O bits are zero except RNGOUT
		clrf	PORTC			; initialize port C I/O bits
		movlw	.16				; set Timer 2 period for 120KHz PWM (8.16MHz/(4*17))
		movwf	PR2
		movlw	b'11001000'		; enable peripheral interrupts & interrupt on change
		movwf	INTCON
		clrf	T1GCON			; disable Timer 1 gate function
		clrf	T1CON			; disable Timer 1 with prescale=0 (time ref)
		clrf	T2CON			; disable Timer 2 with prescale=0 (120KHz PWM)
		clrf	CPSCON0			; disable capacitive sensing oscillator
		clrf	TMR0			; clear Timer 0
		clrf	TMR1H			; clear Timer 1
		clrf	TMR1L
		clrf	TMR2			; clear Timer 2

		banksel	OSCCON			; select BANK1 for osc, interrupts, & A/D converter
		movlw	b'01110010'		; use internal osc at 8MHz
		movwf	OSCCON			; set oscillator control
		movlw	b'00010000'		; to start with frequency close to 8.16MHz
		movwf	OSCTUNE			; set oscillator tune setting
		movlw	b'00101010'		; ICSCLK, VPP, 60Hz inputs, LEDGRN, C1OUT/T0CK, TP120K outputs
		movwf	TRISA			; define I/O port A directions
		movlw	b'00100010'		; RX, X10 are inputs, TW523O, PWM120, TPDIAG, LEDRED are outputs

;	movlw	b'00100011'		; leave LEDGRN as an input to monitor D/A out

		movwf	TRISC			; define I/O port C directions
		movlw	b'00111111'		; enable weak pull-ups, T0 Clk is C1Out\, no T0 prescale
		movwf	OPTION_REG		; set option register
		movlw	b'00000001'		; enable just Timer 1 overflow interrupt
		movwf	PIE1
		clrf	PIE2			; no other interrupts enabled
		clrf	ADCON0			; disable A/D converter
		clrf	ADCON1			; 
		movlw	b'00010111'		; set WDT for 2 sec timeout (31KHz/65536)
		movwf	WDTCON

		banksel	APFCON			; select BANK2 for comparator and D/A
		clrf	APFCON			; set all standard pin functions
		clrf	SRCON0			; make sure S-R latch is disabled
		clrf	SRCON1
		clrf	FVRCON			; disable fixed voltage reference
		movlw	b'10000000'		; enable D/A converter and not the output test point

;	movlw	b'10100000'		; enable D/A converter and output test point

		movwf	DACCON0
		movlw	.6				; set D/A converter to .94V  (6/32) x 5V
		movwf	DACCON1

		movlw	b'10100100'		; enable comparator 1 to monitor X10 BPF input
		movwf	CM1CON0
		movlw	b'00010001'		; no interrupts, DAC reference, use C12IN1- input 
		movwf	CM1CON1
		clrf	CM2CON0			; make sure comparator 2 is disabled
		clrf	CM2CON1
		movlw	b'10000000'		; to enable brown-out protection
		movwf	BORCON			; set brown-out control

		banksel	ANSELA			; select BANK3
		clrf	ANSELA			; all port A pins are for digital I/O
		movlw	b'00000010'		; select X10 bandpass filter input is analog
		movwf	ANSELC			; X10 BPF input is analog, all others are digital I/O
		clrf	TXSTA			; make sure the serial transmitter is disabled
		clrf	RCSTA			; make sure the serial receiver is disabled

		banksel	WPUA			; select BANK4 for weak pull-up control
		movlw	b'00000010'		; to enable weak pull-up on ICSPCLK (mode select)
		movwf	WPUA			; enable port A weak pull-up on ICSPCLK
		clrf	WPUC			; no pull-up on port C

		banksel	CCPR1L			; select BANK5 for PWM control
		movlw	.6				; for 3.12uS pulsewidth
		movwf	CCPR1L			; set PWM duty cycle
		clrf	CCP1CON			; keep PWM disabled for now
		clrf	PWM1CON			; no PWM delay between phases
		clrf	CCP1AS			; no PWM auto-shutdown
		clrf	PSTR1CON		; keep PWM output disabled for now

		banksel	IOCAF			; select BANK7 for interrupt on change
		movlw	b'00100000'		; to enable interrupt on change for ZCD
		movwf	IOCAP			; set interrupt enable for ZCD positive edge
		movwf	IOCAN			; set interrupt enable for ZCD negative edge
		clrf	IOCAF			; clear any existing IOC flag bits
		movlw	b'11010110'		; to set reference clock output to 127.5KHz
		movwf	CLKRCON			; enable clock reference output

		clrf	BSR				; back to BANK0
		movlw	rambgn			; get pointer to beginning of RAM workspace
		movwf	FSR0L			; set indirect pointer to RAM workspace
		clrf	FSR0H
ramclr	clrf	INDF0			; clear RAM
		incf	FSR0L,F			; next address
		movlw	ramend			; get pointer to end of RAM
		subwf	FSR0L,W			; to end of RAM?
		btfss	STATUS,Z		; skip if RAM is all cleared
		goto	ramclr			; loop

		movlw	b'00100000'		; preset frequency error to mid-range
		movwf	frqerr
		movlw	.48				; start with AGC level at .94V (5V full-scale)
		movwf	agclvl
		movlw	b'00110000'		; OSCTUNE preset close to 8.16MHz
		movwf	nxttune			; also save at RAM workspace
		movlw	.60				; 60 cycles = 1 sec.
		movwf	startw			; set startup window for configuration reset to 1 second
		movwf	modtim			; set mode command timer to transmit powerup @ end of window
		movlw	moddef			; get mode command default byte
		movwf	modflg			; set defaults
		movlw	hsedef			; get default housecode
		movwf	hsemod			; set housecode
		movlw	b'00001100'		; housecode P
		movwf	rhouse			; save for power-on acknowledge
		movlw	b'00011011'		; status ON
		movwf	xcmdcd			; save for power-on acknowledge
		clrf	stsflg			; clear status flags
		clrf	xmtflg			; clear transmit flags
		bsf		xmtflg,xmtcmd	; set flag to transmit power-on status
		bsf		T2CON,TMR2ON	; start PWM timer
		retfie					; return from initialize & enable interrupts


;		SUBROUTINE TO PROCESS RECEIVED DATA
;
;		This routine is called after the end of the X10 reception window.
;		A count of 48 or more in cyccnt normally indicates a logic "1".

;		Process X10 48-cycle fixed decode

rcvinp	bcf		stsflg,rcvbit	; set received bit status to "0"
		bcf		stsflg,inpbit	; also set input bit status to "0"
		btfss	stsflg,dgioin	; skip if a digital input is being processed
		goto	fixdcd			; process powerline signals if not
		btfsc	modflg,colabt	; skip if collision abort is disabled
		goto	fixdcd			; process powerline signals if not
		btfsc	xmtflg,outbit	; skip if transmitted bit is "0"
		goto	bitone			; force a received "1"
		goto	bitzer			; force a received "0"

fixdcd	movfw	cyccnt			; get received bit cycle count
		addlw	.208			; C set if => 48 cycles in the window
chkdcd	btfsc	STATUS,C		; skip if not a logic "1"
		goto	bitone			; process the "1" bit
		addlw	.36				; C set if 12 or more cycles in the window
		btfsc	STATUS,C		; skip if not 12 or more cycles of noise
		call	incagc			; increase AGC level by 1 DAC bit

;		Count gap length up to 255 half cycles (clear line)

bitzer	incf	gapcnt,W		; increment gap count
		btfss	STATUS,Z		; skip if no overflow
		movwf	gapcnt			; save new gap count
		movlw	.252			; to check for a 2 cycle gap
		addwf	gapcnt,W		; has there been a 2 cycle gap?
		btfss	STATUS,C		; skip if gap is at least 2 cycles long
		goto	savzer			; save zero in the receive shift register
		bcf		xmtflg,colisn	; ensure collision is cleared
		bcf		stsflg,rcverr	; ensure receive error is cleared
		bcf		xmtflg,xmtmsg	; ensure transmit message flag is cleared
		bcf		xmtflg,rptmsg	; ensure repeat message flag is cleared
		bcf		stsflg,dimcmd	; ensure dim command flag is cleared
		bcf		stsflg,xtdcmd	; ensure extended command flag is cleared
		movlw	clrsrc			; to mask all X10 source flags
		andwf	stsflg,F		; clear all X10 source status flags

; 		Shift zero into the receive shift register

savzer	bcf		STATUS,C		; restore bit status to "0" for shift
		goto	bitsav			; save bit in shift register

bitone	bsf		stsflg,inpbit	; set input bit status to "1"
		incf	onecnt,F		; increment number of "1" bits
		clrf	gapcnt			; clear gap count for a "1"
		btfss	xmtflg,xmtmsg	; skip if a message is being transmitted
		bsf		stsflg,linein	; set flag for receiving a powerline signal
		btfss	xmtflg,xmtmsg	; skip if a message is being transmitted
		goto	bitled			; flash LED for a received "1"
		btfsc	xmtflg,outbit	; skip if transmitter was off this half cycle
		goto	bitled			; flash LED for a transmitted "1"
		bsf		xmtflg,colisn	; indicate a collision was detected
		btfsc	modflg,colrpt	; skip if no auto-repeat following a collision
		bsf		xmtflg,xmtcmd	; set flag to re-transmit when line is clear
		movlw	ledcol			; number of flashes to indicate a collision
		call	ledset			; set LED count for number of flashes
bitled	movf	ledcnt,F		; check if LED is being flashed
		btfsc	STATUS,Z		; skip if so
		bcf		PORTC,LEDRED	; turn on green LED for a received "1"
		bsf		STATUS,C		; restore bit status to "1" for shift
bitsav	rlf		rcvsr1,F		; shift bit into receive shift register
		rlf		rcvsr2,F
		rlf		rcvsr3,F
		rlf		rcvsr4,F
		rlf		rcvsr5,F
		rlf		rcvsr6,F
		rlf		rcvsr7,F
		rlf		rcvsr8,F
		movf	rcvcnt,F		; test receive bit count
		btfss	STATUS,Z		; skip & check for start if zero
		goto	rcvchk			; check if received full word
		movfw	rcvsr1			; get most recent 8 bits
		andlw	b'00111111'		; mask all but last 6 bits
		btfsc	STATUS,Z		; skip if not 6 zeros in a row (full gap)
		goto 	rcvgap			; clear counts for a gap
		andlw	b'00001111'		; mask all but last 4 bits
		movwf	temp1			; save it for additional checks
		xorlw	b'00001110'		; check for "start" data pattern (1110)?
		btfsc	STATUS,Z		; skip if not a valid "start"
		goto	rcvbgn			; begin receive after receiving a valid start

;		Check for a bad start pattern

		movfw	temp1			; recall potential start pattern 
		xorlw	b'00000010'		; to check for just one "1" bit
		btfsc	STATUS,Z		; skip if not "0010"
		goto	errset			; indicate receive error for "0010"
		movfw	temp1			; recall potential start pattern
		xorlw	b'00000110'		; to check for just two "1" bits
		btfsc	STATUS,Z		; skip if not "0110"
		goto	errset			; indicate receive error for "0110"
		movfw	temp1			; recall potential start pattern
		xorlw	b'00001111'		; to check for just four "1" bits with no "0"
		btfsc	STATUS,Z		; skip if not "1111"
		goto	errset			; indicate receive error if all 1's
		goto	rcvclr			; clear receive counts if not

rcvbgn	movlw	.4				; preset rcvcnt to 4 bits for valid "start"
		movwf	rcvcnt
		movlw	.2				; preset 1/0 ratio to 2 (for 1110 start)
		movwf	onezer
		clrf	msglen			; no message size determined yet
		clrf	onecnt			; clear count of "1" data bits
		return					; done with bit processing

;		Maintain 1/0 ratio when receiving first half of message

rcvchk	btfss	rcvsr1,0		; skip if received bit was a "1"
		decf	onezer,F		; decrement 1/0 ratio if it was a "0"
		btfsc	rcvsr1,1		; skip if received bit was a "0"
		incf	onezer,F		; increment 1/0 ratio if it was a "1"

;		Check if all data bits have been received

		incf	rcvcnt,F		; increment received bit count
		movfw	rcvcnt			; get received bit count
		btfss	stsflg,xtdcmd	; skip if an extended command
		addlw	.40				; adjust to check for 22-bit standard message
		addlw	.193			; extended: 62+193=255; normal 22+40+193=255
		btfsc	STATUS,C		; skip and check for errors until received 22 or 62 bits
 		goto	rcvout			; no error check after receiving all of first half

;		Check for an error in first 22 or 62 bits (should always be 1-3 after start)

		btfsc	onezer,7		; skip if 1/0 ratio is positive
		goto	errset			; set error flag if negative overflow
		movfw	onezer			; get 1/0 ratio
		btfsc	STATUS,Z		; skip if > 0 (0 = not enough "1"s)
		goto	errset			; set error flag if not enough "1"s
		addlw	.252			; 4 + 252 -> carry set (too many "1"s)
		btfss	STATUS,C		; skip & set error if too many "1"s
		goto	rcv22b			; check if received exactly 22 bits

errset	bsf		stsflg,rcverr	; set error flag
		call	incagc			; increase AGC level by 1 DAC bit
		goto	rcvclr			; clear receive counts

;		Check if exactly 22 bits have been received

rcv22b	movfw	rcvcnt			; get received bit count
		addlw	.234			; check for receiving exactly 22 bits
		btfss	STATUS,C		; skip if received at least 22 bits
		return					; just return if not
		btfss	STATUS,Z		; skip & process if it has received exactly 22 bits
		goto	rcvout			; just output if received more than 22 bits
		clrf	outcnt			; ensure output count is cleared

;		Check for a X10 command storm

rpt22b	btfsc	xmtflg,shutdn	; skip if not already in shutdown
		goto	rcvs10			; set clear line recovery at 10 sec
		movlw	.2				; for 30 commands per minute average
		btfsc	modflg,xmt3ph	; skip & allow 30 commands a minute for single phase
		movlw	.4				; allow only 15 commands per minute average for 3-phase
		addwf	x10cnt,W		; add to X10 traffic count
		btfss	STATUS,C		; skip if X10 transmit storm
		goto	rcvsct			; save X10 traffic count
		bsf		xmtflg,shutdn	; set shutdown flag for command storm
		movlw	b'10001100'		; continuous LED flash for command storm
		iorwf	ledcnt,F		; start LED flashing

;		Substitue "P Status OFF" for command that tripped the storm threshold

		movlw	b'00111010'		; Start pattern & 1st bit of 1100 P housecode
		movwf	rcvsr3
		movlw	b'10010110'		; Remainder of house code & 1st bit "Status Off"
		movwf	rcvsr2
		movlw	b'10100110'		; Remainder of 11101 "Status Off"
		movwf	rcvsr1

rcvs10	movlw	.10				; set clear line recovery at 10 sec	
rcvsct	movwf	x10cnt			; save X10 traffic count

;		Check that no extra "1" bits were received due to a collision
	
		movfw	onecnt			; get number of "1" bits received
		addlw	.247			; were there 9 "1" bits in the data meassage?
		btfss	STATUS,Z		; skip if exactly 9 data bits
		goto	msgbad			; message is bad if not
		call	unpack			; unpack received word
		movfw	rkeycd			; get received key code
		btfss	xmtflg,xtbmsg	; skip if transmitting an internal message
		movwf	xcmdcd			; save for possible transmission
		xorwf	oldkey,W		; same as the prior key code?
		btfss	STATUS,Z		; skip if so
		goto	rcvcmd			; check for command if different key code
		movfw	rhouse			; get received house code
		xorwf	oldhse,W		; same as prior house code?
		btfss	STATUS,Z		; skip if so
rcvcmd	call	cmdchk			; check for mode command

;		Check for extended message codes (01111 or 11001)

		movlw	b'00001111'		; to check for extended code
		xorwf	rkeycd,W		; was it the extended code?
		btfsc	STATUS,Z		; skip & check for extended data if not
		goto	rcvxtm			; set flag for receiving an extended message
		movlw	b'00011001'		; to check for extended data
		xorwf	rkeycd,W		; was it the extended data code?
		btfss	STATUS,Z		; skip if extended data code
		goto	set22b			; set standard 22-bit message if not extended
rcvxtm	bsf		stsflg,xtdcmd	; set flag to indicate receiving an extended command
		movlw	.84				; 31-bit extended message (for end of receive)
		movwf	msglen			; set message length
		movlw	.62				; 
		movwf	outcnt			; set digital output bit count
		goto	rcvout			; no repeat
		
set22b	movlw	.44				; 22-bit fixed length message (repeated)
		movwf	msglen			; set message length
		movwf	outcnt			; set digital output bit count

;		Set repeat enable flag in repeat mode for standard 22-bit messages

		btfss	modflg,rptena	; skip if repeater function is enabled
		goto	rcvout			; just set bit status
		btfss	stsflg,linein	; skip if receiving a powerline X10 command
		goto	rcvout			; just set bit status
		btfsc	stsflg,dimcmd	; skip if not a bright/dim sequence
		goto	rcvout			; bright/dim repeats use internal transmit
		movfw	rkeycd			; get received key code
		xorwf	oldkey,W		; same as the prior key code?
		btfsc	STATUS,Z		; skip and set flag to repeat message if not
		btfss	modflg,onerpt	; skip if no repeat of already repeated message
		bsf		xmtflg,rptmsg	; enable repeat of standard 22-bit message

rcvdim	movlw	b'00001001'		; to check for "DIM" command
		xorwf	rkeycd,W		; is it "DIM"?
		btfsc	STATUS,Z		; skip if not "DIM"
		goto	dimset			; set bright/dim if "DIM"
		movlw	b'00001011'		; to check for "BRIGHT" command
		xorwf	rkeycd,W		; is it "BRIGHT"?
		btfsc	STATUS,Z		; skip if not "BRIGHT"
dimset	bsf		stsflg,dimcmd	; set flag indicating received a bright/dim command

rcvout	btfsc	rcvsr3,5		; skip if next bit is a "0"
		bsf		stsflg,rcvbit	; indicate received bit is a "1"

rcvlen	movf	msglen,F		; test message length
		btfss	STATUS,Z		; skip if message length has not been determined
		goto	chkend			; check end of message if it has

		movfw	rcvsr1			; recall most recent 8 bits
		andlw	b'00111111'		; mask all but last 6 bits
		btfss	STATUS,Z		; is it a gap (6 zeros in a row)?
		goto	chknxt			; check for next "start" if not
		movfw	rcvcnt			; get received bit count
		addlw	.16				; 16 bits left in message
		movwf	msglen			; set message length
		return

chknxt	andlw	b'00001111'		; mask all but last 4 bits
		xorlw	b'00001110'		; check for next "start" data pattern (1110)?
		btfss	STATUS,Z		; skip if "start"
		return					; done with bit processing if not

		movfw	rcvcnt			; get received bit count
		addlw	.17				; 18 bits left in message (allow for trailing 0)
		movwf	msglen			; set message length
		return

msgbad	bsf		stsflg,rcverr	; indicate a receive error for a bad message
		goto	clrmsg			; clear message

chkend	movfw	msglen			; recall message length
		subwf	rcvcnt,W		; is message complete (C=1 if so)?
		btfss	STATUS,C		; skip if message is complete
		return					; just return if not
		btfss	stsflg,xtdcmd	; skip if an extended command
		goto	clrmsg			; clear message if not
		movfw	rcvcnt			; get received bit count
		addlw	.132			; check for end of extended message 62+62+132=256
		btfss	STATUS,C		; skip if extended message repeat is done
		return					; wait until complete

clrmsg	btfsc	xmtflg,rptmsg	; skip if not repeating a message
		bcf		xmtflg,xmtmsg	; ensure transmit message flag is cleared
rcvgap	movlw	lederr			; number of flashes to indicate a receive error
		btfsc	stsflg,rcverr	; skip if error flag is not set
		call	ledset			; set LED count for number of flashes
		bcf		stsflg,rcverr	; clear error flag
rcvclr	clrf	rcvcnt			; clear counters
		clrf	onecnt
		clrf	onezer			; clear 1/0 ratio
		bcf		stsflg,rcvbit	; set received bit to "0"
		bcf		xmtflg,rptmsg	; ensure repeat message flag is cleared
		return					; done with bit processing


;		SUBROUTINE TO UNPACK RECEIVED DATA
;
;		Decodes received message into house code & key code
;
;		22-bit data in rcvsr3-rcvsr2-rcvsr1: 001110HH HHHHHHKK KKKKKKKK
;		Assumes bank 0 is selected at entry.

unpack	clrf	BSR				; select Bank 0
		movfw	rhouse			; get prior house code
		movwf	oldhse			; save it
		movfw	rkeycd			; get prior key code
		movwf	oldkey			; save it
		clrf	rhouse			; clear prior house code
		clrf	rkeycd			; clear prior key code
		btfsc	rcvsr3,1		; skip if house code MSB is 0
		bsf		rhouse,3		; set house code MSB
		btfsc	rcvsr2,7		; skip if next house code bit is 0
		bsf		rhouse,2		; set next house code bit
		btfsc	rcvsr2,5		; skip if next house code bit is 0
		bsf		rhouse,1		; set next house code bit
		btfsc	rcvsr2,3		; skip if house code LSB is 0
		bsf		rhouse,0		; set house code LSB 
		btfsc	rcvsr2,1		; skip if key code MSB bit is 0
		bsf		rkeycd,4		; set key code MSB
		btfsc	rcvsr1,7		; skip if next key code bit is 0
		bsf		rkeycd,3		; set next key code bit
		btfsc	rcvsr1,5		; skip if next key code bit is 0
		bsf		rkeycd,2		; set next key code bit
		btfsc	rcvsr1,3		; skip if next key code bit is 0
		bsf		rkeycd,1		; set next key code bit
		btfsc	rcvsr1,1		; skip if key code LSB is 0
		bsf		rkeycd,0		; set key code LSB (function select bit)
		return


;		SUBROUTINE TO PROCESS XTB MODE COMMANDS

;		Requires 5-2-3-X-ON/OFF where X is mode setting to change.
;		Max interval between digits is 4 seconds.
;		An incorrect digit will cause an error.
;		Keys cannot be accepted faster than X10 transmit - about .5 sec each.
;
;			8 enables transmit on all 3 phases (X10 boost, digital, & repeat)
;			7 enables repeater function
;			6 selects only one repeat for each command (no ping-pong)
;			5 enables auto re-transmit of repeat following collision
;			4 selects abort transmission when collision is detected (default on)
;			3 is used to program housecode after ALL OFF reset (5-2-3-3-ON)
;			2 selects delayed transmit to reduce potential for "dimmer flicker"
;			1 selects return all bits received (no error checking)


cmdchk	clrf	BSR				; select Bank 0
		movf	startw,F		; is startup window is over?
		btfsc	STATUS,Z		; skip if not
		goto	cmdcnt			; check for mode command if so
		movlw	b'00000001'		; to test for "All Units Off"
		movwf	xcmdcd			; save it to transmit acknowledgement
		xorwf	rkeycd,W		; is it the correct command?
		btfss	STATUS,Z		; skip if so
		goto	cmdbad			; invalid command if not

		clrf	startw			; clear startup window
		bsf		hsemod,hseulk	; unlock housecode for change
		movlw	.240			; set mode command timeout for 4 seconds
		movwf	modtim			; start timer
		goto	resmod			; restore default to EEPROM

cmdcnt	movfw	modcnt			; get number of valid digits received
		movwf	temp1			; save in temp
		movlw	b'00000010'		; to check for "5"
		btfsc	STATUS,Z		; skip if at least one digit was received
		goto	chkdgt			; check first digit
		movlw	b'00011100'		; to check for "2"
		decf	temp1,F			; was just one digit received?
		btfsc	STATUS,Z		; skip if not
		goto	chkdgt			; check second digit
		movlw	b'00000100'		; to check for "3"
		decf	temp1,F			; were just two digits received?
		btfsc	STATUS,Z		; skip if not
		goto	chkdgt			; check third digit
		decf	temp1,F			; were just three digits received?
		btfss	STATUS,Z		; skip & get mode select if so
		goto	cmdset			; check for on/off set function
		btfss	hsemod,hseulk	; skip if housecode is not locked
		goto	cmdmod			; get mode select
		movlw	b'00000101'		; to check for "ON" command
		xorwf	rkeycd,W		; is it "ON"?
		btfsc	STATUS,Z		; skip if not "ON"
		goto	chgcod			; change housecode
		bcf		hsemod,hseulk	; lock housecode to prevent it from being changed
cmdmod	btfsc	rkeycd,0		; skip if keycode is a digit
		goto	cmdbad			; invalid code if not
		rrf		rkeycd,W		; get key code (1-16) into 4 LSBs
		call	x10hex			; convert to hexidecimal (0-F)
		addlw	.248			; check if 0-7 (keys 1-8)
		btfsc	STATUS,C		; skip & save valid code if keys 1-8
		goto	moderr			; indicate an error if keys 9-16
		movfw	rkeycd			; recall key code (1-8)
		movwf	modsel			; save as mode select

		goto	nxtkey			; digit accepted & start timeout
cmdset	movlw	.120			; set mode command timeout for 2 seconds
		movwf	modtim			; start timer
		movlw	b'00000101'		; to check for "ON" command
		xorwf	rkeycd,W		; is it "ON"?
		btfsc	STATUS,Z		; skip if not "ON"
		goto	modset			; set selected mode bit
		movlw	b'00000111'		; to check for "OFF" command
		xorwf	rkeycd,W		; is it "OFF"?
		btfsc	STATUS,Z		; skip if not "OFF"
		goto	modclr			; clear selected mode bit
		movlw	b'00011111'		; to check for "Status Request" command
		xorwf	rkeycd,W		; is it "Status Request"?
		btfsc	STATUS,Z		; skip if not
		goto	modack			; check and acknowledge mode status

chkdgt	xorwf	rkeycd,W		; is it the correct digit?
		btfss	STATUS,Z		; skip if so
		goto	cmdbad			; invalid mode command
		btfsc	hsemod,hseulk	; skip if housecode is locked
		goto	nxtkey			; accept all housecodes when unlocked
		movfw	rhouse			; get received housecode
		xorwf	hsemod,W		; is it accepted to program mode commands?
		btfss	STATUS,Z		; skip if so
		goto	cmdbad			; reject command if not
nxtkey	incf	modcnt,F		; indicate correct digit was received
		movlw	.240			; set mode command timeout for 4 seconds
		movwf	modtim			; start timer
		return

moderr	movlw	ledmod			; number of flashes for a mode command timeout
		call	ledset			; set LED count for number of flashes
cmdbad	clrf	modcnt			; clear mode command digit count
		clrf	modsel			; clear mode select
		clrf	modtim			; clear mode timer to cancel 4 second window
		return

;		Change housecode accepted for ON and OFF mode commands

chgcod	movfw	rhouse			; get housecode (MSB is zero)
		movwf	hsemod			; set & lock housecode accepted for mode commands
		banksel	EEADR			; select BANK3 to access EEPROM
		movlw	EEHSE			; point to housecode address in EEPROM
		movwf	EEADR			; set EEPROM address to write
		movfw	hsemod			; recall housecode from global RAM
		call	writee			; write housecode into EEPROM
		clrf	BSR				; back to BANK0
		clrf	modcnt			; clear mode command digit count
		movlw	b'00011011'		; to acknowledge "Status ON"
		movwf	xcmdcd			; save it to be transmitted
		bsf		xmtflg,xmtcmd	; set flag to transmit acknowledgement
		return


;		SUBROUTINE TO SET SELECTED MODE BIT

;		Enter with modsel = mode bit to set (0-7)

modset	rrf		modsel,W		; get mode select & shift right
		call	x10hex			; convert X10 code to decimal value
		movwf	temp0			; save in temporary workspace
		clrf	temp1			; clear bit pointer
		incf	temp0,F			; adjust 0-7 to 1-8
		bsf		STATUS,C		; set carry bit to shift 1 into LSB
modset1	rlf		temp1,F			; shift bit pointer left
		decfsz	temp0,F			; decrement and skip if bit pointer is correct
		goto	modset1			; loop until correct bit
		movfw	temp1			; get bit to set
		iorwf	modflg,F		; set flag bit
		goto	savmod			; write new mode status into EEPROM


;		SUBROUTINE TO CLEAR SELECTED MODE BIT

;		Enter with modsel = mode bit to clear (0-7)

modclr	rrf		modsel,W		; get mode select & shift right
		call	x10hex			; convert X10 code to decimal value
		movwf	temp0			; save in temporary workspace
		clrf	temp1			; clear bit pointer
		incf	temp0,F			; adjust 0-7 to 1-8
		bsf		STATUS,C		; set carry bit to shift 1 into LSB
modclr1	rlf		temp1,F			; shift bit pointer left
		decfsz	temp0,F			; decrement and skip if bit pointer is correct
		goto	modclr1			; loop until correct bit
		movfw	temp1			; get bit to clear
		xorlw	b'11111111'		; flip all bits
		andwf	modflg,F		; clear flag bit

;		Write mode flags & XTB housecode into EEPROM

savmod	bsf		xmtflg,xmtcod	; set flag to transmit keycode number
resmod	bsf		xmtflg,xmtcmd	; set flag to transmit status
		clrf	modcnt			; clear mode command digit count

		banksel	EEADR			; select BANK3 to access EEPROM
		movlw	EEMODE			; get mode flags address in EEPROM
		movwf	EEADR			; set EEPROM address to read
		movfw	modflg			; get mode flags in RAM
		call	writee			; write new mode select into EEPROM

		incf	EEADR,F			; set next EEPROM address to read
		movfw	hsemod			; get XTB housecode from RAM
		andlw	b'00001111'		; mask MSBs
		call	writee			; write housecode into EEPROM
		clrf	BSR				; back to BANK0

;		SET PWM POWER LEVEL

pwmpwr	btfss	modflg,xmt3ph	; skip if 3-phase transmit is selected
		goto	nrmpwm			; set normal PWM width if not
lowpwm	movlw	.4				; for 2.08uS low power PWM pulsewidth
		goto	setpwm			; set PWM pulsewith
nrmpwm	movlw	.6				; for 3.12uS default PWM pulsewidth
setpwm
		banksel	CCPR1L			; select BANK5 for PWM
		movwf	CCPR1L			; set correct PWM duty cycle
		clrf	BSR				; back to BANK0
		return					; done


;		Perform the actual write into EEPROM

;		Address must be set up in EEADR before called
;		Data passed in W is only written if EEPROM data must be changed

writee	bsf		EECON1,RD		; read EEPROM location
		xorwf	EEDAT,F			; does data have to be changed?
		btfsc	STATUS,Z		; skip & write data if so
		return					; no write is necessary

		movwf	EEDAT			; save data to write to EEPROM
		bsf		EECON1,WREN		; enable write
disint	bcf		INTCON,GIE		; disable interrupts
		btfsc	INTCON,GIE		; skip if they are disabled
		goto	disint			; try again
		movlw	h'55'			; write 55 AA pattern to unlock write
		movwf	EECON2
		movlw	h'AA'
		movwf	EECON2
		bsf		EECON1,WR		; start write
		bsf		INTCON,GIE		; enable interrupts

savdly	btfsc	EECON1,WR		; skip if write has completed
		goto	savdly			; wait for EEPROM write to complete
		bcf		EECON1,WREN		; disable write
		return


;		SUBROUTINE TO CHECK & ACKNOWLEDGE STATUS OF SELECTED MODE BIT

;		Enter with modsel = X10 keycode for mode bit to check
;		Returns xcmdcd = X10 keycode for Status ON or Status OFF

modack	rrf		modsel,W		; get mode select & shift right
		andlw	b'00001111'		; mask all but 4 LSBs
		call	x10hex			; convert X10 code to decimal value
		movwf	temp0			; save in temporary workspace
		btfsc	temp0,3			; skip if decimal value is 0-7
		goto	moderr			; error if above 7
		incf	temp0,F			; adjust 0-7 to 1-8
		movfw	modflg			; get mode flags
		movwf	temp1			; save in temp workspace
modack3	rrf		temp1,F			; shift LSB of workspace into C
		decfsz	temp0,F			; decrement and skip if C is correct status bit
		goto	modack3			; loop until correct bit

		movlw	b'00011011'		; to acknowledge "Status ON"
		btfss	STATUS,C		; skip if selected mode is ON
		movlw	b'00011101'		; to acknowledge "Status OFF"
		movwf	xcmdcd			; save it to be transmitted
		bsf		xmtflg,xmtcmd	; set flag to transmit status
		return


;		SUBROUTINE TO PROCESS TRANSMIT DATA
;
;		This routine is called to set up the next bit to be transmitted

xmtbit	btfss	xmtflg,xmtcod	; skip if keycode must be transmitted
		goto	xmtccd			; just transmit command code if not
		movf	xmtcnt,F		; check if message must be loaded
		btfsc	STATUS,Z		; skip if not
		goto	xmtmod			; set up mode status for transmit
		movlw	.228			; for 28 bits left (22 data + 6 gap)
		addwf	xmtcnt,W		; check if 2nd half must be loaded
		btfss	STATUS,Z		; skip if so
		goto	xmtout			; output next bit if not
		goto	xmtnu2			; set up 2nd half

xmtmod	call	modack			; check and acknowledge mode status
		movfw	rhouse			; get received house code
		movwf	xhouse			; save it for transmit
		movfw	modsel			; get mode select (X10 code)
		movwf	xkeycd			; save it for transmit
		movlw	.50				; transmit 50 bits (22 + 22 + 6 gap)
		movwf	xmtcnt			; set up transmit count
xmtnu2	call	packit			; assemble 1st half of transmit message
		goto	xmtout			; output first bit

xmtccd	movf	xmtcnt,F		; check if message must be loaded
		btfsc	STATUS,Z		; skip if not
		goto	xmtsts			; set up mode status for transmit
		movlw	.228			; for 28 bits left (22 data + 6 gap)
		addwf	xmtcnt,W		; check if 2nd half must be loaded
		btfss	STATUS,Z		; skip if so
		goto	xmtout			; output next bit if not
		goto	xmtst2			; set up 2nd half

xmtsts	movfw	rhouse			; get received house code
		movwf	xhouse			; save it for transmit
		movfw	xcmdcd			; get mode ON/OFF Status (X10 code)
		movwf	xkeycd			; save it for transmit
		movlw	.50				; transmit 50 bits (22 + 22 + 6 gap)
		btfsc	stsflg,dimcmd	; skip if not receiving a dim command
		movlw	.22				; transmit only 22 bits for dim command
		movwf	xmtcnt			; set up transmit count
xmtst2	call	packit			; assemble 1st half of transmit message

xmtout	bcf		xmtflg,outbit	; make sure output bit has been cleared
		btfsc	stsflg,xtdcmd	; skip if not an extended command
		goto	xmtxtd
		btfsc	xmtsr3,5		; skip if next bit is a "0"
		bsf		xmtflg,outbit	; set output bit to a logic "1"
		goto	xmtsft			; shift transmit register 1 bit

xmtxtd	btfsc	rcvsr8,5		; skip if next bit is a "0"
		bsf		xmtflg,outbit	; set output bit to a logic "1"
		goto	deccnt			; decrement transmit count

xmtsft	bcf		STATUS,C		; make sure "C" is zero
		rlf		xmtsr1,F		; shift "0" into transmit shift register
		rlf		xmtsr2,F
		rlf		xmtsr3,F
deccnt	decfsz	xmtcnt,F		; decrement transmit bit count & skip if end of message
		return					; just return if not

		btfsc	xmtflg,xmtcod	; skip if not transmit keycode
		goto	xmtnu0			; just clear acknowledge number flag
		bcf		xmtflg,xmtmsg	; clear transmit message flag
		bcf		xmtflg,xtbmsg	; clear internally generated message flag
		bcf		xmtflg,xmtcmd	; clear transmit command flag
xmtnu0	bcf		xmtflg,xmtcod	; clear transmit keycode flag
		return


;		SUBROUTINE TO ASSEMBLE SHIFT REGISTER DATA FOR TRANSMIT
;
;		Encodes house code and key code into transmit message
;			xhouse = house code for transmit
;			xkeycd = key code for transmit
;		22-bit data in xmtsr3-xmtsr2-xmtsr1: 001110HH HHHHHHKK KKKKKKKK
;		Assumes bank 0 is selected at entry.

packit	movlw	b'00001110'		; set up 4-bit start pattern
		movwf	xmtsr3			; save in MSB of transmit shift register
		movfw	xhouse			; get 4-bit house code
		call	expx10			; convert to expanded X10 code
		movwf	xmtsr2			; save in 2nd byte of transmit shift register
		rrf		xkeycd,W		; get 5-bit key code & shift off command/function bit
		call	expx10			; convert remaining 4 bits to expanded X10 code
		movwf	xmtsr1			; save in LSB of transmit shift register

		bcf		STATUS,C		; make sure carry bit is cleared
		btfss	xkeycd,0		; skip if command/function bit is "1"
		bsf		xmtsr3,7		; set MSB to roll a "0-1" into command/function bits
		btfsc	xkeycd,0		; skip if command/function bit "0"
		bsf		STATUS,C		; set carry to roll a "1-0" into command/function bits

		rlf		xmtsr1,F		; roll 1st command/function bit into LSB of shift register
		rlf		xmtsr2,F
		rlf		xmtsr3,F		; shifts 2nd command/function bit into carry
		rlf		xmtsr1,F		; and around into LSB of transmit shift register
		rlf		xmtsr2,F
		rlf		xmtsr3,F
		return


;		SUBROUTINES TO CONTROL 120KHZ PWM TRANSMITTER

;		Control 120KHz by TW523 Data Input

chkdin	clrf	BSR				; switch to BANK0
		btfsc	xmtflg,xtbmsg	; continue if not transmitting an internal message
		return
		btfsc	xmtflg,rptmsg	; continue if not repeating a message
		return
		btfsc	PORTC,TW523I	; skip if TW523 input is ON (transmit a logic "1")
		goto	xmtoff			; ensure PWM transmitter is off
xmtond	bsf		stsflg,dgioin	; make sure digital input flag is set

;		Turn on 120KHz PWM transmitter

xmton
		banksel	PSTR1CON		; select BANK5 for PWM control
		btfsc	PSTR1CON,STR1C	; skip if PWM transmitter is not on yet
		goto	xmton5			; done if already on

		clrf	BSR				; back to BANK0
		bsf		xmtflg,outbit	; set flag indicating a "1" will be transmitted
		bsf		xmtflg,xmtmsg	; set flag indicating a message is being transmitted

;		Check if transmitter start should be delayed to prevent dimmer flicker

xmton1	bsf		xmtflg,outbit	; set flag indicating a "1" will be transmitted
		btfss	modflg,xmtdly	; skip if transmit burst should be delayed
		goto	xmton2			; just turn on transmitter if not
		movfw	TMR1H			; get Timer 1 high byte
		addlw	.7				; past beginning of delayed transmit window for 1st burst?
		btfsc	STATUS,C		; skip and check if in range of 2nd & 3rd bursts
		goto	xmton2			; turn on transmitter
		addlw	.203			; past the end of the 3rd 3-phase window?
		btfsc	STATUS,C		; skip if so
		goto	xmton5			; done if already on

xmton2	btfsc	xmtflg,shutdn	; skip if no shutdown due to command storm
		goto	xmtoff			; make sure transmitter is off
		btfss	xmtflg,colisn	; skip if a collision has occurred
		goto	xmton3			; turn on transmitter
		btfsc	modflg,colabt	; skip if no abort on a collision
		goto	xmton5			; return without turning transmitter on

xmton3	movf	ledcnt,F		; check if LED is being flashed
		btfss	STATUS,Z		; skip and turn on red LED for "1" when transmitting
		goto	xmton4			; leave LED status alone when flashing
		bsf		PORTC,LEDRED	; turn on red LED
		bcf		PORTA,LEDGRN

xmton4
		banksel	PSTR1CON		; select BANK5 for PWM control
		movlw	.6				; for 3.12uS pulsewidth
		movwf	CCPR1L			; set PWM duty cycle
		movlw	b'00001100'		; set PWM active high mode with single output
		movwf	CCP1CON			; set PWM mode
		clrf	PWM1CON			; no PWM delay between phases
		clrf	CCP1AS			; no PWM auto-shutdown
		movlw	b'00010100'		; to enable PWM P1C output at next PWM period
		movwf	PSTR1CON		; enable 120KHz PWM

xmton5	clrf	BSR				; back to BANK0
		return

;		Turn off 120KHz PWM transmitter

xmtoff
		banksel	PSTR1CON		; select BANK5 for PWM control
		clrf	PSTR1CON		; disable PWM P1C output (also indicates xmtr is off)
		clrf	CCP1CON			; shut off PWM
		clrf	BSR				; back to BANK0
		bcf		PORTC,PWM120	; make sure PWM output is a "0"
		return


;		MAIN PROGRAM TIMING LOOP

;		This loops through completely for each half cycle of the powerline

start	call	initiz			; call initialization sequence
		goto	main			; go to main timing loop

;		Disable Timer 1 and inhibit driver when zero-crossing is not detected

main00	clrf	BSR				; make sure BANK0 is selected
		bcf		T1CON,TMR1ON	; turn off Timer 1
		clrf	TMR1H			; clear Timer 1
		clrf	TMR1L

;		Main timing loop

main
		banksel	PSTR1CON		; select BANK5 for PWM control
		clrf	PSTR1CON		; disable PWM P1C output (also indicates xmtr is off)
		clrf	CCP1CON			; also make sure the PWM is off
		clrf	BSR				; back to BANK0

maindy	movfw	TMR1H			; get Timer 1 high byte
		addlw	.202			; is it 250uS before the next zero crossing? (8.16 MHz)
		btfss	STATUS,C		; skip if so
		goto	maindy			; loop if not

		banksel	TRISA			; select BANK1 for port direction register
		bsf		TRISA,ZCDIN		; set ZCD to an input
		clrf	BSR				; back to BANK0
		banksel	IOCAF			; select BANK7 for interrupt on change
		movlw	b'00100000'		; to enable interrupt on change for ZCD
		movwf	IOCAP			; set interrupt enable for ZCD positive edge
		movwf	IOCAN			; set interrupt enable for ZCD negative edge
		clrf	IOCAF			; clear any existing IOC flag bits
		clrf	BSR				; back to BANK0
		bsf		INTCON,IOCIE	; ensure ZCD interrupt on change is enabled

mainzc	movfw	TMR1H			; get Timer 1 high byte
		btfsc	STATUS,Z		; skip if Timer 1 is running
		goto	mainzc			; loop & wait for zero-crossing if Timer 1 is not running

main01	btfss	T1CON,TMR1ON	; skip if Timer 1 is running
		goto	main			; stop and wait for next zero crossing if not
		movfw	TMR1H			; get Timer 1 high byte
		addlw	.202			; .20 mS before the next zero crossing? (at 8.16 clk)
		btfss	STATUS,C		; skip and output digital data if so
		goto	main01			; loop until just before ZCD

;		Send input bit to digital I/O port early to accommodate Stargate

		bsf		PORTC,TW523O	; make sure TW523 output pulse is high
		btfsc	stsflg,dgioin	; skip if not receiving a digital input
		goto	outdig			; output error checked digital data if so
		btfss	modflg,allbit	; skip if receiving all bits
		goto	outdig			; output error checked digital data if not
		btfsc	stsflg,inpbit	; skip if input bit status is "0"
		bcf		PORTC,TW523O	; set output pulse low for a "1"
		goto	main02			; done with digital output

outdig	movfw	outcnt			; get digital output bit count
		btfsc	STATUS,Z		; skip if a message is being output
		goto	main02			; no output
		btfsc	stsflg,rcvbit	; skip if received bit is a "0"
		bcf		PORTC,TW523O	; set output pulse low for a "1"
		decf	outcnt,F		; decrement bit count

main02	movfw	TMR1H			; get Timer 1 high byte
		addlw	.10				; check for timer preload
		btfsc	STATUS,C		; skip if not
		goto	main03			; begin processing next half cycle if so
		addlw	.168			; past next transmit window with no zero crossing?
		btfss	STATUS,C		; skip if so
		goto	main02			; loop while waiting for zero crossing
		goto	main00			; stop & disable transmitter if no zero crossing

;		Make sure comparator and D/A converter are enabled

main03	lsrf	agclvl,W		; get current 1/2 AGC level
		movwf	temp1			; save in temp
		lsrf	temp1,F			; divide by 2
		lsrf	temp1,W			; divide by 2 again for a total of 8
		banksel	DACCON1			; select BANK2 for comparator and D/A
		movwf	DACCON1			; make sure D/A is set to current AGC level
		movlw	b'10000000'		; enable D/A converter and not the output test point

;	movlw	b'10100000'		; to enable D/A with test point output

		movwf	DACCON0			; make sure D/A converter is enabled
		movlw	b'10100100'		; enable comparator 1 to monitor X10 BPF input
		movwf	CM1CON0
		movlw	b'00010001'		; no interrupts, DAC reference, use C12IN1- input 
		movwf	CM1CON1

		banksel	PSTR1CON		; select BANK5 for PWM control
		btfsc	PSTR1CON,STR1C	; skip if PWM transmitter is off
		goto	clrwin			; just sample pulses if transmitter is on
		clrf	BSR				; back to BANK0
		bcf		PORTC,PWM120	; make sure PWM output is a "0"

		btfsc	xmtflg,xtbmsg	; skip if not transmitting an internal message
		goto	dlyxmt			; no pre-window source check if transmitting a message
		btfsc	xmtflg,rptmsg	; skip if not repeating a message
		goto	dlyxmt			; also no pre-window source check if repeating a message

;		Check if currently processing a command

		movlw	allsrc			; to check if a X10 signal is already being processed
		andwf	stsflg,W		; check all receive flags
		btfsc	STATUS,Z		; skip if a X10 command is already being processed
		goto	chk523			; check for a new X10 command prior to reception window

		btfsc	stsflg,dgioin	; skip if not already receiving a digital input
		goto	chkdio			; check for next digital input bit

		btfsc	stsflg,dimcmd	; skip if not receiving a dim command
		goto	chkerl			; check if early window for dim repeat
		btfss	stsflg,xtdcmd	; skip if receiving an extended command
		goto	dlywin			; delay for reception window if normal powerline signal

		movfw	outcnt			; get digital output bit count
		addlw	.234			; allow for 9 more bits in extended command (62-18=44)
		btfsc	STATUS,C		; skip if at start bit for second half of doublet
		goto	dlywin			; no check until after first half of extended command

chkerl	btfsc	xmtflg,rptmsg	; skip if command is not already being repeated
		goto	clrwin			; no need to wait for beginning of sample window

;		Check for early X10 burst when receiving a bright/dim sequence or extended code

erlylp	btfss	T1CON,TMR1ON	; skip if Timer 1 is running
		goto	main			; stop and wait for next zero crossing if not

;		Wait for end of pre-window 250uS after zero crossing

		clrf	TMR0			; clear Timer 0 at start of pre-window

erlyl1	movfw	TMR0			; read Timer 0 to check for pulses
		addlw	.248			; have there been 8 pulses?
		btfsc	STATUS,C		; skip if not
		goto	erly12			; turn on transmitter to repeat dim if 8 pulses

		movfw	TMR1H			; get Timer 1 high byte
		addlw	.7				; end of pre-window check?
		btfss	STATUS,C		; skip if so
		goto	erlyl1			; loop until end of reception window
		goto	bgnwin			; begin sampling powerline during window

erly12	call	xmton			; turn on transmitter

		btfss	stsflg,xtdcmd	; skip if receiving an extended command
		bcf		PORTC,TW523O	; set output pulse low for a "1"
		bsf		xmtflg,rptmsg	; set flag to indicate a repeat is in process
		btfss	stsflg,dimcmd	; skip if receiving a dim command
		goto	xtdrpt			; set repeat of extended command

;		Set up repeat of dim command

dimrpt	movfw	rhouse			; get received house code
		movwf	xhouse			; save it for transmit
		movfw	xcmdcd			; get bright/dim code
		movwf	xkeycd			; save it for transmit
		call	packit			; assemble transmit message
		call	xmtccd			; set up first start bit for transmit
		movlw	.22				; preset rcvcnt to 22 bits to process dim command when called
		movwf	rcvcnt
		movlw	.2				; set 1/0 ratio to 2 (for 1110 start)
		movwf	onezer
		movlw	.44				; set 44-bit standard message length
		movwf	msglen
		movlw	.9				; set 9 "1" bits for no error
		movwf	onecnt
		bsf		xmtflg,xtbmsg	; repeat dim command as an internal message
		goto	clrwin			; no need to wait for beginning of sample window

;		Set up repeat of extended command

xtdrpt	movlw	.62				; set 62-bit extended message length
		movwf	xmtcnt			; set number of bits to transmit
		goto	clrwin			; no need to wait for beginning of sample window

;		Check for digital input if already processing a digital input message

chkdio	movfw	TMR1H			; get Timer 1 high byte
		addlw	.7				; at beginning of X10 reception window?
		btfsc	STATUS,C		; skip and continue checking source if not
		goto	bgnwin			; switch to monitoring the powerline & begin reception window
		btfsc	PORTC,TW523I	; skip if TW523 input is ON (transmit a logic "1")
		goto	chkdio			; loop
		btfsc	modflg,allbit	; skip if not receiving all bits
		bcf		PORTC,TW523O	; set output pulse low for a "1"
		call	xmtond			; set flag for digital I/O and turn on transmitter
		goto	dlyxmt			; check for delayed transmitter start

;		Check for digital input until reception window starts 250 uS after zero crossing

chk523	btfss	PORTC,TW523I	; skip if TW523 input is off (transmit a logic "0")
		goto	dxmton			; indicate source is data input port & turn on transmitter

chkwin	movfw	TMR1H			; get Timer 1 high byte
		addlw	.7				; at beginning of X10 reception window?
		btfss	STATUS,C		; skip if so 
		goto	chk523			; continue checking TW523 input if not
		goto	bgnwin			; switch to monitoring the powerline & begin reception window

dxmton	btfsc	modflg,allbit	; skip if not receiving all bits
		bcf		PORTC,TW523O	; set output pulse low for a "1"
		call	xmtond			; set flag for digital input & turn on transmitter

;		If transmit is pending, turn on transmitter after delay (to prevent flicker)

dlyxmt
		banksel	PSTR1CON		; select BANK5 for PWM control
		btfsc	PSTR1CON,STR1C	; skip if PWM transmitter is off
		goto	clrwin			; begin reception window if transmitter is already on
		clrf	BSR				; back to BANK0
		btfss	xmtflg,outbit	; skip if transmitter should be on this cycle
		goto	dlywin			; delay until reception window if not transmitting
		btfss	modflg,xmtdly	; skip if transmit burst should be delayed
		goto	dlyxon			; turn on transmitter if not
dlyxlp	movfw	TMR1H			; get Timer 1 high byte
		addlw	.7				; at beginning of delayed transmit window?
		btfss	STATUS,C		; skip if so
		goto	dlyxlp			; wait for beginning of transmit window
dlyxon	call	xmton2			; turn on transmitter
		movlw	.64				; 64 pulses
		movwf	cyccnt			; force receiving a "1" if transmitter is on
		goto	main12			; skip over reception window when tranmitting

;		Delay until beginning of X10 reception window

dlywin	movfw	TMR1H			; get Timer 1 high byte
		addlw	.7				; at beginning of X10 reception window?
		btfss	STATUS,C		; skip if so
		goto	dlywin			; wait for beginning of reception window

bgnwin
		banksel	PSTR1CON		; select BANK5 for PWM control
		btfss	PSTR1CON,STR1C	; skip if PWM transmitter is on	
		bcf		PORTC,PWM120	; make sure PWM output is a "0"

;		Start X10 reception window

clrwin	clrf	BSR				; make sure BANK0 is selected
		bsf		PORTC,TPDIAG	; turn diagnostic pulse on
		clrf	TMR0			; clear Timer 0 counting cycle count
		clrf	cyccnt			; clear received cycle count

;		Wait for end of reception window 1000uS after zero crossing

main10	btfss	T1CON,TMR1ON	; skip if Timer 1 is running
		goto	main			; stop and wait for next zero crossing if not
		btfsc	PORTC,TW523I	; skip if TW523 input is ON (transmit a logic "1")
		goto	main11			; check for end of reception window if not
		call	xmtond			; set flag for digital I/O and turn on transmitter
		movlw	.64				; 64 pulses
		movwf	cyccnt			; force receiving a "1" if transmitter is on
		goto	main12			; skip over reception window when tranmitting

main11	movfw	TMR1H			; get Timer 1 high byte
		addlw	.1				; past the X10 reception window?
		btfss	STATUS,C		; done if so
		goto	main10			; loop for X10 reception window
		movfw	TMR0			; read Timer 0 to check for pulses
		movwf	cyccnt			; save at cycle count
		movwf	tstcnt			; save for diagnostic test pulse train
		bcf		PORTC,TPDIAG	; turn diagnostic pulse off

;		Delay until timer overflow (end of X10 transmit window)

main12	btfss	T1CON,TMR1ON	; skip if Timer 1 is running
		goto	main			; stop and wait for next zero crossing if not
		btfsc	TMR1H,7			; check for timer overflow
		goto	main12			; loop until timer overflow

;		Shut off PWM & digital output at end of X10 window

		call	xmtoff			; turn off transmitter
		btfss	modflg,allbit	; skip if receiving all bits
		bsf		PORTC,TW523O	; make sure TW523 output pulse is high
		movf	ledcnt,F		; check if LED is being flashed
		btfss	STATUS,Z		; skip if not
		goto	main13			; leave LED status alone when flashing
		btfsc	xmtflg,xmtmsg	; skip if not transmitting a message
		goto	main13			; leave LED on when transmitting a message
		movfw	cyccnt			; recall cycle count
		addlw	.208			; check if received a "1"
		btfss	STATUS,C		; skip & leave LED on if so
		bsf		PORTC,LEDRED	; turn off green LED

;		Sample AGC 1.25 mS to 2.0 mS after zero crossing

main13	btfss	stsflg,linein	; skip if receiving a powerline input
		goto	main14			; run AGC check when not receiving an X10 command
		btfss	stsflg,rcverr	; skip & run AGC check if error receiving X10 command
		goto	main20			; no AGC check if receiving valid X10 command

main14	btfss	T1CON,TMR1ON	; skip if Timer 1 is running
		goto	main			; stop and wait for next zero crossing if not
		movfw	TMR1H			; get Timer 1 high byte
		addlw	.254			; is it 1.25 mS after the last zero crossing (at 8.16MHz clk)
		btfss	STATUS,C		; skip to begin AGC sample if so
		goto	main13			; loop if not
		movlw	.248			; sample AGC until 2.4 mS after the last zero crossing
		movwf	agcend			; set up AGC cycle end
		bsf		PORTC,TPDIAG	; turn diagnostic pulse on
		call	agcloop			; run AGC loop
		bcf		PORTC,TPDIAG	; turn diagnostic pulse off
		movfw	agclvl			; get AGC level
		call	tstpls			; output AGC level as pulses to dignostic test point

;		Check if 3-phase transmission is enabled

main20	btfss	modflg,xmt3ph	; skip if 3-phase transmit is selected
		goto	main40			; just check AGC window if no 3-phase is selected

;		Check X10 & Digital In during 2nd 3-Phase Window

main22	btfss	T1CON,TMR1ON	; skip if Timer 1 is running
		goto	main			; stop and wait for next zero crossing if not
		movfw	TMR1H			; get Timer 1 high byte
		addlw	.242			; past the start of the 2nd 3-phase window?
		btfss	STATUS,C		; skip if so
		goto	main22			; loop until the beginning of the window

		bsf		PORTC,TPDIAG	; turn diagnostic pulse on
		btfss	xmtflg,xtbmsg	; skip if transmitting an internal message
		goto	main23			; check if repeating
		btfsc	xmtflg,outbit	; skip if output bit is zero
		call	xmton			; turn on the transmitter
		goto	main25			; ignore inputs while transmitting

main23	btfss	xmtflg,rptmsg	; skip if repeating a message
		goto	main24			; check other inputs
		btfsc	stsflg,rcvbit	; skip if repeated bit is "0"
		call	xmton			; turn on the transmitter
		goto	main25			; ignore inputs while repeating
	
main24	btfsc	xmtflg,xtbmsg	; skip if not transmitting an internal message
		goto	main25			; ignore inputs while transmitting
		btfsc	xmtflg,rptmsg	; skip if not repeating a message
		goto	main25			; ignore inputs while repeating
		
		btfsc	stsflg,dgioin	; skip if not receiving a digital input
		call	chkdin			; check digital input

main25	btfss	T1CON,TMR1ON	; skip if Timer 1 is running
		goto	main			; stop and wait for next zero crossing if not
		movfw	TMR1H			; get Timer 1 high byte
		addlw	.234			; past the end of the 2nd 3-phase window?
		btfss	STATUS,C		; skip if so
		goto	main24			; loop until end of the window

		bcf		PORTC,TPDIAG	; turn diagnostic pulse off
		call	xmtoff			; turn off the transmitter (also indicates xmtr is off)

;		Check X10 & Digital In during 3rd 3-Phase Window

main32	btfss	T1CON,TMR1ON	; skip if Timer 1 is running
		goto	main			; stop and wait for next zero crossing if not
		movfw	TMR1H			; get Timer 1 high byte
		addlw	.220			; past the start of the 3rd 3-phase window?
		btfss	STATUS,C		; skip if so
		goto	main32			; loop until the beginning of the window

		bsf		PORTC,TPDIAG	; turn diagnostic pulse on
		btfss	xmtflg,xtbmsg	; skip if transmitting an internal message
		goto	main33			; check if repeating
		btfsc	xmtflg,outbit	; skip if output bit is zero
		call	xmton			; turn on the transmitter
		goto	main35			; ignore inputs while transmitting

main33	btfss	xmtflg,rptmsg	; skip if repeating a message
		goto	main34			; check other inputs
		btfsc	stsflg,rcvbit	; skip if repeated bit is "0"
		call	xmton			; turn on the transmitter
		goto	main35			; ignore inputs while repeating
	
main34	btfsc	xmtflg,xtbmsg	; skip if not transmitting an internal message
		goto	main35			; ignore inputs while transmitting
		btfsc	xmtflg,rptmsg	; skip if not repeating a message
		goto	main35			; ignore inputs while repeating
		
		btfsc	stsflg,dgioin	; skip if not receiving a digital input
		call	chkdin			; check digital input

main35	btfss	T1CON,TMR1ON	; skip if Timer 1 is running
		goto	main			; stop and wait for next zero crossing if not
		movfw	TMR1H			; get Timer 1 high byte
		addlw	.212			; past the end of the 3rd 3-phase window?
		btfss	STATUS,C		; skip if so
		goto	main34			; loop until end of the window

		bcf		PORTC,TPDIAG	; turn diagnostic pulse off
		call	xmtoff			; turn off the transmitter (also indicates xmtr is off)


;		Process received data and set up next bit to be transmitted

main40	call	rcvinp			; process received data
		bcf		xmtflg,outbit	; make sure prior output bit status was cleared
		btfsc	xmtflg,xtbmsg	; skip if not transmitting an internal message
		call	xmtbit			; set up next transmit bit

		movfw	tstcnt			; recall X10 cycle count for diagnostic readout
		call	tstpls			; output cycle count as pulses to diagnostic test point
		btfsc	xmtflg,colisn	; skip if no collision was detected
		bsf		PORTC,TPDIAG	; turn diagnostic pulse on to indicate a collison

		goto	main			; loop and wait for next zero crossing


;		AGC LOOP

;		Uses internal comparator reference to measure noise

agcloop decf	agclvl,F		; decrease AGC level by 1 count
		movfw	agclvl			; get current AGC level
		addlw	.248			; to check if .15V or higher (255 = 5V)
		movlw	.8				; to set AGC level to .15V minimum
		btfss	STATUS,C		; skip if the AGC level .15V or higher
		movwf	agclvl			; set AGC level to .15V minimum

		lsrf	agclvl,W		; get 1/2 current AGC level
		movwf	temp2			; save in temp
		lsrf	temp2,F			; divide by 2
		lsrf	temp2,W			; divide by 2 again for a total of 8
		banksel	DACCON1			; select BANK2 for comparator and D/A
		movwf	DACCON1			; set D/A to current AGC level
		movlw	b'10000000'		; enable D/A converter and not the output test point

;	movlw	b'10100000'		; to enable D/A with test point output

		movwf	DACCON0			; make sure D/A converter is enabled
		movlw	b'10100100'		; enable comparator 1 to monitor X10 BPF input
		movwf	CM1CON0
		movlw	b'00010001'		; no interrupts, DAC reference, use C12IN1- input 
		movwf	CM1CON1
		clrf	BSR				; back to BANK0
		clrf	TMR0			; clear timer noise count

agclp1	movfw	TMR1H			; get Timer 1 high byte
		addwf	agcend,W		; check for end of AGC window
		btfsc	STATUS,C		; continue AGC sample window if not
		goto	agclp2			; end AGC window & set AGC level to lower value
		movfw	TMR0			; read Timer 0 count (=> 8 cycles for too much noise)
		addlw	.248			; has it read 8 noise pulses?
		btfss	STATUS,C		; skip and check with higher AGC level if so
		goto	agclp1			; loop until 8 pulses or end of AGC window

;		Increase AGC level if over 8 noise pulses at this AGC level

		call	incagc			; increase AGC level by 1 DAC bit
		lsrf	agclvl,W		; get 1/2 current AGC level
		movwf	temp2			; save in temp
		lsrf	temp2,F			; divide by 2
		lsrf	temp2,W			; divide by 2 again for a total of 8
		banksel	DACCON1			; select BANK2 for comparator and D/A
		movwf	DACCON1			; set new D/A value
		clrf	BSR				; back to BANK0
		clrf	TMR0			; clear timer noise count
		goto	agclp1			; loop until 8 pulses or end of AGC window

agclp2	return					; done with AGC loop

;		Increase AGC level by 1 DAC bit

incagc	movfw	agclvl			; get current AGC level
		addlw	.8				; increase AGC level by .15V
		movwf	agclvl			; save new value
		addlw	.52				; check if already 4V
		movlw	.204			; for maximum AGC level of 4V
		btfsc	STATUS,C		; skip if less than 4V
		movwf	agclvl			; set AGC to 4V maximum
		return

;		Diagnostic to output a series of pulses to TP1 equal to the value passed in W

tstpls	clrf	BSR				; make sure BANK0 is selected
		movwf	temp2			; save pulse count at temp
		incf	temp2,W			; to insure 1 pulse minimum
		btfss	STATUS,Z		; skip if overflow
		movwf	temp2			; save pulse count +1

tstplp	bsf		PORTC,TPDIAG	; output a positive going pulse
		bcf		PORTC,TPDIAG
		decfsz	temp2,F			; done?
		goto	tstplp			; loop if not
		return


;		EEPROM DATA

EE		CODE	0xF000			; (location found by trial and error)
		DE		"XTB-523_V1.07 "; program name and version

		CODE	0xF010
		DE		moddef			; EEPROM storage for first mode flag byte
		DE		hsedef			; EEPROM storage for selected housecode

		END						; directive 'end of program'
