;********************************************************************
;																	*
;	XTB-IIR Repeater version 1.25									*
;																	*
;	Filename:		XTB-IIR_125.asm									*
; 	Date:			03/22/19										*
;	File Version:	rev 1.25/E										*
;																	*
;	Author:			Jeff Volp										*
;	Company:		JV Digital Engineering 							*
;																	*
;	This program is a significant upgrade for the XTB-II Repeater.  *
;	A PIC16F1823 replaces the PIC16F684 used previously, and much	*
;	of the code is ported over from the XTB-523.  The major purpose	*
;	of this change is to allow sharing one PIC program between all	*
;	versions of the XTB-II PCB.  The PIC will configure itself for	*
;	whichever board it is plugged into, but the jumper should be	*
;	placed in the 1.20+ position for boards with a firmware jumper.	*
;	This program includes the following changes from 1.12:			*
;																	*
;	Version 1.20 changes:											*
;		The 50Hz/60Hz powerline FLL should lock more reliably.		*
;		The LED will glow dimly when powered & locked to 50Hz/60Hz.	*
;		The mode option to disable AGC just switches to low gain.	*
;		   (The AGC is always enabled.)								*
;		There are individual enables for pre and post AGC windows.	*
;		   (The "pre" window can be disabled in an Insteon system.)	*
;		A third AGC sample is made during X10 receive window "0".	*
;		An individual mode status can be checked through the LED.	*
;		Selected housecodes can be prevented from being repeated.	*
;	Version 1.21 for Version 1.10 PCB with ZCD on xfmr secondary.	*
;		This determines PCB type & adjusts timing accordingly.		*
;	Version 1.22 changes:											*
;		Restored 3-phase transmit from the digital port.			*
;		Corrected timing of 2nd & 3rd transmit windows at 50Hz.		*
;		Removed erroneous trailing bright/dim at end of a sequence.	*
;	Version 1.23 changes:											*
;		Faster lock to 60Hz input for 1.20, 1.21, & XTB-III PCBs	*
;	Version 1.24 changes:											*
;		Improves AGC operation when there is high powerline noise	*
;	Version 1.25 chages:											*
;		Correct no repeat on housecode C & K after power interrupt.	*
;																	*
;********************************************************************

	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

MODESW	EQU		H'05'			; (RA5) Mode Select input - 0 for normal mode (GND)
XMTINH	EQU		H'05'			; (RA5) Transmit inhibit for PCB prior to 1.30
TW523O	EQU		H'04'			; (RA4) TW523 Data Out - 0 for opto-isolator ON
TW523I	EQU		H'03'			; (RA3) TW523 Data In - 0 for opto-isolator LED ON
X10120	EQU		H'02'			; (RA2) 120KHz output from X10 transmitter AC receptacle (NOT USED in V1.30 PCB)
X10XIN	EQU		H'01'			; (RA1) 120KHz input from X10 transmitter AC receptacle
ZCDIN	EQU		H'00'			; (RA0) Zero Crossing Detector - 50/60Hz reference input
PWM120	EQU		H'05'			; (RC5) 120KHz PWM 120KHz reference
PWMOUT	EQU		H'04'			; (RC4) 120KHz PWM output (directly drives power stage in V1.30 PCB)
LEDOUT	EQU		H'03'			; (RC3) LED output - 1 turns LED on
LOGAIN	EQU		H'02'			; (RC2) Powerline bandpass amplifier gain control - 1 for low gain
PWRXIN	EQU		H'01'			; (RC1)	120KHz input from powerline bandpass amplifier
AGCTST	EQU		H'00'			; (RC0) AGC Test Point (was CMPREF Comparator Reference for PIC16F684)


; 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)
bstcnt		RES		1			; X10 Boost pulse count during pre-reception window
lincnt		RES		1			; line input pulse count during pre-reception window
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)
cooldy		RES		1			; cooldown delay - determines how long next shutdown will be
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 "982" 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
agcdac		RES		1			; 5-bit comparator threshold (adjusted for high threshold)
thdcnt		RES		1			; threshold limit count
ramend

			UDATA_SHR	0x70

cyccnt		RES		1			; received bit cycle count
nxttune		RES		1			; next setting for OSCTUNE
temp0		RES		1			; global general purpose register
temp1		RES		1			; global general purpose register (for AGC & mode command)
temp2		RES		1			; global general purpose register (for AGC loop)

stsflg		RES		1			; general status flags

modjmp		EQU		H'07'		; "1" indicates the mode jumper is not installed
pcbv20		EQU		H'06'		; "1" selects PCB 1.31(+) firmware jumper at 1.20+ (PWM on pin 5)
pcbinv		EQU		H'05'		; "1" selects inverted PWM for PCB versions before 1.22
zcdxfm		EQU		H'04'		; "1" indicates ZCD is on transformer secondary (version 1.10 PCB)
;			EQU		H'03'		; "1" 
;			EQU		H'02'		; "1"
shutdn		EQU		H'01'		; "1" indicates shutdown due to command storm
freqok		EQU		H'00'		; "1" indicates the clock has locked to the powerline

rcvflg		RES		1			; receive 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)
xtdcmd		EQU		H'04'		; "1" indicates receiving an extended command
dimcmd		EQU		H'03'		; "1" indicates receiving a bright/dim command
bx10in		EQU		H'02'		; "1" indicates a X10 Boost input is being received
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'00011111'	; used to check if any X10 source flags are set (including dim & extended)
clrsrc		EQU		b'11111000'	; used to clear all X10 source flags

xmtflg		RES		1			; transmitter status flags

spare1		EQU		H'07'		; "1" 
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 4-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

modflg1		RES		1			; mode flags (copied from EEPROM)
   variable	moddef1=b'10000001'	; default to repeater enabled, sequential dims enabled
rptena		EQU		H'07'		; 16 = 1 enables repeater function
realtm		EQU		H'06'		; 15 = 1 (TBD) selects real-time bit-by-bit boost with no error checking
xmt3ph		EQU		H'05'		; 14 = 1 enables transmit on all 3 phases (X10 boost, digital, & repeat)
xmtdly		EQU		H'04'		; 13 = 1 selects delayed transmit to reduce potential for "dimmer flicker"
xmtlow		EQU		H'03'		; 12 = 1 selects transmit at reduced power level (default for 3-phase xmit)
colrpt		EQU		H'02'		; 11 = 1 enables auto re-transmit of repeat following collision
colabt		EQU		H'01'		; 10 = 1 selects abort transmission when collision is detected
rptdim		EQU		H'00'		;  9 = 1 enables special handling when repeating bright/dim commands

modflg2		RES		1			; more mode flags (copied from EEPROM)
  variable	moddef2=b'00101100'	; default high gain with normal threshold, enable both AGC sample windows

onerpt		EQU		H'07'		;  8 = 1 selects only one repeat for each command (no ping-pong)
hitraf		EQU		H'06'		;  7 = 1 storm threshold 60/min. avg; 0 allows 30/min. avg.
higain		EQU		H'05'		;  6 = 1 selects maximum sensitivity with AGC enabled
thldhi		EQU		H'04'		;  5 = 1 selects high AGC threshold
pstagc		EQU		H'03'		;  4 = 1 enables post X10 transmit AGC window
preagc		EQU		H'02'		;  3 = 1 enables pre zero-crossing AGC window
pgmhse		EQU		H'01'		;  2 = 1 is used to program housecode after ALL OFF reset
allbit		EQU		H'00'		;  1 = 1 selects return all bits received (no error checking)

hsemsk1		RES		1			; housecode mask byte 1 (1 = disable repeat on this housecode)
  variable	mskdef1=b'00000000'	; default repeat enabled on all housecodes

hsemsk2		RES		1			; housecode mask byte 2 (1 = disable repeat on this housecode)
  variable	mskdef2=b'00000000'	; default repeat enabled on all housecodes 

hsemod		RES		1			; housecode accepted for mode commands & PCB configuration
  variable	hsedef=b'00001100'	; default P, changed code used for 9-8-2-2-ON after ALL OFF reset

hseulk		EQU		H'07'		; unlock flag set at ALL OFF reset to allow house code to be changed
mskulk		EQU		H'06'		; unlock flag set to allow housecode repeat disable to be changed

EEMODE		equ		H'0010'		; location of mode flags in EEPROM
EERINH		equ		EEMODE+2	; location of housecode repeat inhibit in EEPROM
EEHSE		equ		EEMODE+4	; location of housecode and PCB select in EEPROM

;			Error Indications

ledmod		equ		.10			; 5 LED flashes for a mode command timeout
ledcol		equ		.8			; 4	LED flashes for a collision on transmit
lederr		equ		.6			; 3 LED flashes for an error receiving an incoming X10 command
ledmsk		equ		.4			; 2 LED flashes for a masked housecode

;	******	LINE FREQUENCY SELECT FOR 50 or 60 Hz	******

ac50hz		equ		.0			; set to zero for 60Hz, non zero for 50Hz

;**********************************************************************

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

	bcf		PORTA,MODESW	; 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,IOCAP0	; skip if it was a zero crossing interrupt
		goto	unknwn			; unidentified interrupt if not
		bcf		IOCAF,IOCAF0	; clear interrupt flag
		clrf	BSR				; back to Bank0

;		Calculate line frequency period by adding each half cycle

		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
		movlw	.231			; to subtract 25 from MSB so no overflow @ 50Hz
		addwf	periodH,F		; adjust so MSB = 172.8 @ 60Hz or 224.4 @ 50Hz)
		movfw	TMR1H			; get Timer 1 MSB
		addwf	periodH,F		; add to last half cycle MSB
		movlw	.238			; preset Timer 1 to -1.1 mS (at 15.84MHz clk)
		btfsc	stsflg,zcdxfm	; skip if not the early transformer type ZCD
		movlw	.237			; use more delay on each edge for transformer ZCD
		btfss	PORTA,ZCDIN		; skip if falling edge of AC input (FET inverts)
		movlw	.239			; need less delay for positive zero crossing
		btfsc	stsflg,zcdxfm	; skip if not the early transformer type ZCD
		movlw	.237			; use same delay on each edge for transformer ZCD
		movwf	TMR1H			; set high byte
		clrf	TMR1L			; clear low byte
		bsf		T1CON,TMR1ON	; restart Timer 1

;		Clear watchdog timer

		clrwdt					; clear watchdog timer

;		Turn on transmitter as required & set LED to bright or dim 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	chkpol			; no transmit if not
		goto	x10out			; transmit bit

chkrpt	btfss	xmtflg,rptmsg	; skip if message is being repeated
		goto	chkpol			; no transmit if not
		btfsc	rcvflg,xtdcmd	; skip if not an extended command
		goto	chkxtd			; check extended command
		btfss	rcvflg,rcvbit	; skip & transmit if normal bit is a "1"
		goto	chkpol			; no transmit if not
		goto	x10out			; turn on transmitter
chkxtd	btfss	rcvsr8,5		; skip & transmit if extended bit is a "1"
		goto	chkpol			; no transmit if not

x10out	bsf		xmtflg,outbit	; make sure output bit is set to a "1"
		btfss	modflg1,xmtdly	; skip if transmit burst should be delayed
		call	xmton			; turn transmitter on

;		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

;		Decrement cooldown delay for next command storm

		movf	cooldy,F		; is cooldown delay already zero
		btfss	STATUS,Z		; skip if so
		decf	cooldy,F		; decrement cooldown delay

;		Check busy count to recover from a command storm

		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	stsflg,shutdn	; skip if in command storm shutdown
		goto	intrtn			; return with interrupts enabled

		bcf		stsflg,shutdn	; clear shutdown flag
		movlw	.255			; 255 second window for next shutdown to cause cooldown
		movwf	cooldy			; set cooldown delay window
		clrf	ledcnt			; stop LED flashing
		bcf		PORTC,LEDOUT	; turn off 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	modflg1			; save in RAM for quick access
		incf	EEADR,F			; set next EEPROM address to read
		bsf		EECON1,RD		; read EEPROM location
		movfw	EEDAT			; get 2nd set of mode flags
		movwf	modflg2			; 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 repeat disable byte 1
		movwf	hsemsk1			; 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 repeat disable byte 2
		movwf	hsemsk2			; 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
		clrf	BSR				; back to Bank 0
		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	rkeycd			; clear received key code
		call	moderr			; clear mode command & indicate error if timeout
		goto	intrtn			; return with interrupts enabled if falling edge

;		Using line frequency as a reference, adjust clock to 15.84MHz on rising edge

adjclk	btfsc	periodL,7		; round up MSB if LSB > 127
		incf	periodH,F		; MSB = 172.8 (60Hz) or 224.4 (50Hz) with 238/239 preset
		movfw	periodH			; get period MSB
		clrf	periodL			; clear period measurement
		clrf	periodH
		if		ac50hz == 0		; if not for 50Hz AC powerline
		  addlw	.83				; calculate error at 60Hz: W = 0 at 15.84MHz
		else					; must be for 50Hz
		  addlw	.32				; calculate error at 50Hz: W = 0 at 15.84MHz
		endif
		btfsc	stsflg,zcdxfm	; skip if ZCD is fed directly off the AC input
		addlw	.3				; compensate for 237/237 preset for transformer ZCD
		btfsc	STATUS,Z		; skip if clock needs adjusting
		goto	adjfok			; indicate clock frequency is OK
		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
		goto	ledfls			; check LED flashing

adjfok	bsf		stsflg,freqok	; set flag indicating clock frequency is locked

;		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
		btfsc	ledcnt,0		; skip if flash count LSB is zero
		bsf		PORTC,LEDOUT	; turn on LED
		btfss	ledcnt,0		; skip if flash count LSB is one
ledoff	bcf		PORTC,LEDOUT	; turn off LED
		goto	intrtn			; return with interrupts

;		Process timer interrupt at end of X10 transmission window

timint	bcf		PIR1,TMR1IF		; clear Timer 1 interrupt flag
		btfsc	xmtflg,xmtmsg	; skip and check gain when not transmitting
		goto	oscadj			; no gain change when transmitting

;		Control return signal amplifier gain to keep AGC within range (.45V - 3.0V)

		btfss	modflg2,higain	; skip if high gain for maximum sensitivity
		goto	setlow			; force low gain regardless of AGC level
		movfw	agclvl			; recall current internal AGC threshold
		addlw	.128			; is it above 2.5V? (255 = 5V)
		btfsc	STATUS,C		; skip if < 2.5V (max limit)
		goto	maxthd			; check threshold limit count if > 3V
		movfw	agclvl			; recall current internal AGC threshold
		addlw	.235			; is it > .4? (255 = 5V)
		btfss	STATUS,C		; skip if > .4V (min limit)
		goto	minthd			; check threshold limit count if at minimum
		clrf	thdcnt			; clear threshold limit count if within range
		goto 	oscadj			; done with AGC range check

maxthd	btfsc	PORTC,LOGAIN	; skip if still at high gain
		goto	oscadj			; no change required if already low gain
		incfsz	thdcnt,F		; increment threshold limit count & skip if > 2 seconds
		goto	oscadj			; no change if just transient
		bsf		PORTC,LOGAIN	; switch to low gain
		movlw	.26				; reduce AGC level by a factor of 5 for 5X gain reduction
		movwf	agclvl			; set new internal threshold value (2.5V / 5 => .6V)
		goto	oscadj			; done with gain change

minthd	btfss	PORTC,LOGAIN	; skip if still at low gain
		goto	oscadj			; no chage required if already high gain
		incfsz	thdcnt,F		; increment threshold limit count & skip if > 2 seconds
		goto	oscadj			; no change if just transient
		bcf		PORTC,LOGAIN	; switch to high gain
		movlw	.102			; increase AGC level by a factor of 5 for 5X gain increase
		movwf	agclvl			; set new internal threshold value (.4V x 5 => 2V)
		goto	oscadj			; done with gain change

;		Force low gain for low gain mode

setlow	bsf		PORTC,LOGAIN	; set low gain

;		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

	bsf		PORTA,MODESW	; 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
		movlw	b'00010000'		; set TW523O to a "1", all others to zero
		movwf	PORTA			; initialize port A I/O bits
		movlw	b'00110000'		; set PWM outputs to a "1", all others to zero
		clrf	PORTC			; initialize port C I/O bits
		movlw	.32				; set Timer 2 period for 120KHz PWM (15.84MHz/(4*33)
		movwf	PR2
		clrf	INTCON			; disable interrupts for now
		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'01111010'		; use internal osc at 16MHz
		movwf	OSCCON			; set oscillator control
		movlw	b'00111000'		; to start with frequency close to 15.84MHz
		movwf	OSCTUNE			; set oscillator tune setting
		movlw	b'00101111'		; ZCDIN, X10XIN, X10120, TW523I, MODESW are inputs, TW523O is output
		movwf	TRISA			; define I/O port A directions
		movlw	b'00110010'		; PWRXIN, PWMOUT, PWM120 are inputs, AGCTST, LOGAIN, LEDOUT are outputs
		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
		movlw	b'10001000'		; enable fixed voltage reference set for 2V
		movwf	FVRCON
		movlw	b'10000000'		; enable D/A converter and not the output test point
		movwf	DACCON0
		movlw	.6				; set D/A converter to 1V  (6/32) x 5V
		movwf	DACCON1

		movlw	b'10000100'		; enable comparator 1 to monitor X10 Boost input
		movwf	CM1CON0
		movlw	b'10100000'		; positive transition, fixed reference, use C12IN0- input 
		movwf	CM1CON1
		movlw	b'10000100'		; enable comparator 2 to monitor X10 BPF input
		movwf	CM2CON0
		movlw	b'10010001'		; positive transition, DAC reference, use C12IN1- input 
		movwf	CM2CON1
		movlw	b'10000000'		; to enable brown-out protection
		movwf	BORCON			; set brown-out control

		banksel	ANSELA			; select BANK3
		movlw	b'00000010'		; X10XIN (X10 Boost input) is analog
		movwf	ANSELA			; set X10XIN to analog in, all others are digital I/O
		movlw	b'00000010'		; PWRXIN (powerline X10 bandpass filter) is analog analog
		movwf	ANSELC			; set PWRXIN to analog in, 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'00100000'		; to enable weak pull-up on MODESW (DRVINH on V1.1 & 1.2 PCB)
		movwf	WPUA			; enable port A weak pull-up on MODESW
		clrf	WPUC			; no pull-up on port C

		banksel	CCPR1L			; select BANK5 for PWM control
		movlw	.8				; for 2.08uS pulsewidth (25% duty cycle) for configuration check
		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'00000001'		; 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	CLKRCON			; disable clock reference output

;	movlw	b'11010111'		; to set reference clock output to 123.75KHz
;	movwf	CLKRCON			; FOR TESTING ONLY!  (OUTPUT ON PIN 3)

		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	b'00011000'		; OSCTUNE was preset close to 15.84MHz
		movwf	nxttune			; also save at RAM workspace
		movlw	.48				; start with AGC level at .94V (5V full-scale)
		movwf	agclvl
		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	moddef1			; get first mode command default byte
		movwf	modflg1			; set defaults
		movlw	moddef2			; get second mode command default byte
		movwf	modflg2			; set defaults
		clrf	hsemsk1			; repeat is enabled on all housecodes
		clrf	hsemsk2
		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	rcvflg			; clear receive flags
		clrf	xmtflg			; clear transmit flags
		bsf		T2CON,TMR2ON	; start PWM timer
		call	pcbcfg			; determine the PCB configuration
		call	zcdcfg			; determine ZCD configuration
		bsf		xmtflg,xmtcmd	; set flag to transmit power-on status
		retfie					; return from initialize & enable interrupts


;		DETERMINE PCB CONFIGURATION

;		This routine determines the PCB configuration at power-up.

;			pcbinv = 1 selects inverted PWM for PCB versions before 1.22
;		    pcbv20 = 1 selects PCB 1.31(+) firmware jumper at 1.20+ (PWM on pin 5)

pcbcfg	movlw	.10				; delay 10 AC cycles to allow power supply to charge
		movwf	temp0			; save for 160mS (or 200mS @ 50Hz) delay
pcbc11	btfss	PORTA,ZCDIN		; skip if zero crossing detector is a "1"
		goto	pcbc11			; wait for rising edge
pcbc12	btfsc	PORTA,ZCDIN		; skip if zero crossing detector is a "0"
		goto	pcbc12			; wait for falling edge (to check XMTR at mid cycle)
		decfsz	temp0,F			; skip if startup delay is over
		goto	pcbc11			; loop for startup delay
		clrf	TMR1L			; clear Timer 1
		clrf	TMR1H
		bsf		T1CON,TMR1ON	; start Timer 1

pcbc13	movfw	TMR1H			; get timer 1 MSB
		addlw	.210			; is it 3mS past zero crossing
		btfss	STATUS,C		; skip & check transmitter if so
		goto	pcbc13			; loop until 3mS past zero crossing
		btfss	PORTA,MODESW	; skip if mode switch jumper is not installed (open)
		goto	pcbc30			; check which pin to use for PWM

		bsf		stsflg,modjmp	; set mode jumper flag indicating the pin is open
		bsf		PORTA,XMTINH	; set MODESW/XMTINH to a "1"
		bcf		PORTC,PWMOUT	; set pin 6 PWMOUT to a "0" for positive pulses
		bcf		PORTC,PWM120	; set pin 5 PWM120 to a "0" for positive pulses
		banksel	TRISA			; select BANK1 for port direction
		bcf		TRISA,XMTINH	; set MODESW/XMTINH pin to output (to check for XMTINH)
		bcf		TRISC,PWMOUT	; set PWMOUT to an output
		bcf		TRISC,PWM120	; set PWM120 to an output
		clrf	BSR				; back to BANK0

		movlw	b'00010011'		; select PWM output on both P1A and P1B
		call	pcbxmt			; check if transmitter works with XMTINH = 1
		btfsc	STATUS,C		; skip if XMTINH inhibited transmit (PCB 1.1 & 1.2)
		goto	pcbc30			; check which PWM pin to use for new driver stage

		bcf		PORTA,XMTINH	; set XMTINH = 0
		movlw	b'00010010'		; select PWM output on just P1B
		call	pcbxmt			; check if transmitter works with XMTINH = 0
		bsf		PORTA,XMTINH	; set XMTINH = 1 to disable transmitter
		btfss	STATUS,C		; skip if transmitter works with XMTINH = 0
		goto	pcberr			; pcb configuration error if not
		bsf		PORTC,PWMOUT	; set PWM output pin 6 to a "1"
		bsf		stsflg,pcbinv	; use inverted PWM
		bcf		stsflg,pcbv20	; must be 1.1x firmware
		goto	pcbc32			; stop Timer 1 & return

pcbc30
		banksel	TRISA			; select BANK1 for port direction
		bcf		TRISC,PWMOUT	; make sure PWMOUT is an output
		bcf		TRISC,PWM120	; make sure PWM120 is an output
		clrf	BSR				; back to BANK0
		bcf		stsflg,pcbinv	; use positive PWM
		movlw	b'00010001'		; select PWM output on just P1A (pin 5)
		call	pcbxmt			; check if firmware jumper is in 1.2+ position
		btfss	STATUS,C		; skip if transmit worked on pin 5 (firmware 1.2+)
		goto	pcbc31			; check which PWM pin to use for new driver stage
		bsf		stsflg,pcbv20	; must be 1.2+ firmware
		goto	pcbc32			; stop Timer 1 & return

pcbc31	movlw	b'00010010'		; select PWM output on just P1B (pin 6)
		call	pcbxmt			; check if firmware jumper is in 1.1+ position
		btfss	STATUS,C		; skip if transmit worked on pin 6 (firmware 1.1+)
		goto	pcberr			; pcb configuration error if not
		bcf		stsflg,pcbv20	; must be 1.1+ firmware
pcbc32	bcf		T1CON,TMR1ON	; stop Timer 1
		return

pcberr	bsf		PORTC,LEDOUT	; turn on LED
		goto	pcberr			; lock up if error


;		CHECK TRANSMITTER CONFIGURATION

;		enter with W = PWM steering select to be written to PSTR1CON

;			b'00010010' enables PWM P1B (pin 6)
;			b'00010001' enables PWM P1A (pin 5)
;			b'00010011'	enables both PWM P1A and P1B

;		returns C=1 if transmitter is functioning in this configuration

pcbxmt	clrf	TMR1L			; clear Timer 1
		clrf	TMR1H
		clrf	cyccnt			; clear xmit pulse count

pcbx00	btfss	TMR1H,2			; skip if 260uS passed to allow prior xmit to damp out
		goto	pcbx00			; loop if not

		banksel	PSTR1CON		; select BANK5 for PWM control
		movwf	PSTR1CON		; set PWM steering configuration
		movlw	.16				; for 4.16uS 50% duty cycle pulsewidth
		movwf	CCPR1L			; set PWM duty cycle
		movlw	b'00001100'		; to set PWM active high mode with single output
		movwf	CCP1CON			; set PWM mode
		clrf	BSR				; back to BANK0

pcbx01	btfss	PIR2,C2IF		; skip if a xmit pulse is detected
		goto	pcbx02			; check for time out
		bcf		PIR2,C2IF		; clear the flag bit
		incf	cyccnt,F		; count xmit pulse
pcbx02	btfsc	TMR1H,0			; has Timer 1 incremented by 64uS
		btfsc	STATUS,Z		; done if so
		goto	pcbx01			; loop if not

		banksel	PSTR1CON		; select BANK5 for PWM control
		clrf	PSTR1CON		; disable PWM output
		clrf	CCP1CON			; shut off PWM
		clrf	BSR				; back to BANK0
		movfw	cyccnt			; recall xmit pulse count
		addlw	.251			; check if => 5 pulses (should be 6-8)
		return					; end PCB xmit check (C=1 if xmit worked)


;		DETERMINE ZERO CROSSING DETECTOR CONFIGURATION

;		This subroutine determines the ZCD configuration by checking the
;		comparator threshold.  Versions that use the transformer secondary
;		have a 3.3uF directly on the PIC output.  Versions with the dropping
;		resistor use a RC configuration for the threshold.

;		Returns zcdxfm = 1 for early transformer type ZCD

zcdcfg
		banksel	TRISC			; select BANK1 for port direction
		bcf		TRISC,AGCTST	; make sure comparator reference set to output
		clrf	BSR				; back to BANK0
		bcf		PORTC,AGCTST	; start with comparator reference at "0"
		clrf	temp0			; for time delay
zcddy1	decfsz	temp0			; tick off one loop
		goto	zcddy1			; loop for 194uS
		bsf		PORTC,AGCTST	; set comparator reference to "1"
		bcf		stsflg,zcdxfm	; preset dropping resistor configuration
		btfss	PORTC,AGCTST	; skip if comparator reference is "1" (1K & .47uF)
		bsf		stsflg,zcdxfm	; must be transformer ZCD with 3.3uF on CMPREF
	bcf		PORTC,AGCTST	; set the comparator reference back to "0"

;	banksel	TRISC			; select BANK1 for port direction
;	bsf		TRISC,AGCTST	; set comparator reference to input
;	clrf	BSR				; back to BANK0
		return


;		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		rcvflg,rcvbit	; set received bit status to "0"
		bcf		rcvflg,inpbit	; also set input bit status to "0"
		btfss	rcvflg,dgioin	; skip if a digital input is being processed
		goto	fixdcd			; process powerline signals if not
		btfsc	modflg1,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		rcvflg,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		rcvflg,dimcmd	; ensure dim command flag is cleared
		bcf		rcvflg,xtdcmd	; ensure extended command flag is cleared
		movlw	clrsrc			; to mask all X10 source flags
		andwf	rcvflg,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		rcvflg,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		rcvflg,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	modflg1,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
		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	rcvflg,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		rcvflg,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	stsflg,shutdn	; skip if not already in shutdown
		goto	rcvs10			; set clear line recovery interval
		movlw	.1				; allow 60 commands per minute average
		btfss	modflg1,xmt3ph	; skip & force low threshold if 3-phase transmit
		btfss	modflg2,hitraf	; skip if high traffic threshold
		movlw	.2				; allow 30 commands per minute for default & 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		stsflg,shutdn	; set shutdown flag for command storm
		movlw	b'10001100'		; continuous LED flash for command storm
		iorwf	ledcnt,F		; start LED flashing

;		Substitute "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

rcvstm	movf	cooldy,F		; check if cooldown delay is over
		movlw	.10				; to set clear line recovery at 10 sec (does not effect Z)
		btfsc	STATUS,Z		; skip if not first shutdown (cooldown window is active)
		goto	rcvsct			; set 10 seconds for first shutdown
		movlw	.180			; for 180 second cooldown for 2nd shutdown
		btfsc	modflg1,xmt3ph	; skip if not 3-phase transmit
		goto	rcvsct			; set cooldown delay to 180 seconds for 3-phase
		btfss	modflg2,hitraf	; skip & use 180 second cooldown for high traffic
		movlw	.60				; for 60 second recovery for normal traffic
		goto	rcvsct			; set cooldown delay

rcvs10	movfw	x10cnt			; recall X10 traffic count
		addlw	.246			; check if 10 or more seconds remaining
		movlw	.10				; to set clear line recovery at 10 sec
		btfss	STATUS,C		; skip if X10 traffic count is > 10 seconds
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		rcvflg,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	modflg1,rptena	; skip if repeater function is enabled
		goto	rcvout			; just set bit status
		call	msksts			; get housecode disable status (C=1 for disabled)
		btfss	STATUS,C		; skip if housecode is disabled
		goto	setrpt			; process normally

		movlw	ledmsk			; number of flashes to indicate the housecode is disabled
		call	ledset			; set LED count for number of flashes
		goto	rcvout			; no repeat for disabled housecode

setrpt	btfss	rcvflg,linein	; skip if receiving a powerline X10 command
		goto	rcvout			; just set bit status
		btfsc	rcvflg,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	modflg2,onerpt	; skip if no repeat of already repeated message
		bsf		xmtflg,rptmsg	; enable repeat of standard 22-bit message

rcvdim	btfss	modflg1,rptdim	; skip if enable special handling of bright/dim commands
		goto	rcvout			; no special handling of bright/dim commands
		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		rcvflg,dimcmd	; set flag indicating received a bright/dim command

rcvout	btfsc	rcvsr3,5		; skip if next bit is a "0"
		bsf		rcvflg,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		rcvflg,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	rcvflg,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	btfss	rcvflg,rcverr	; skip if a receive error was detected
		goto	rcvclr			; no error indication if not
		movlw	lederr			; number of flashes to indicate a receive error
		call	ledset			; set LED count for number of flashes
rcvclr	bcf		rcvflg,rcverr	; clear error flag
		clrf	rcvcnt			; clear counters
		clrf	onecnt
		clrf	onezer			; clear 1/0 ratio
		bcf		rcvflg,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
		btfss	rcvsr1,1		; skip if key code LSB is 1 (function code)
		return					; done if not

		bsf		rkeycd,0		; set key code LSB (function select bit)
		btfsc	xmtflg,xmtmsg	; skip if not transmitting a message
		return

		movf	modcnt,F		; check if receiving a valid mode command
		btfsc	STATUS,Z		; skip if so
		bcf		hsemod,mskulk	; lock housecode disable to prevent if from being changed
		return


;		SUBROUTINE TO PROCESS XTB MODE COMMANDS

;		Requires 9-8-2-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.
;
;			16 enables repeater function
;			15 (TBD) selects real-time bit-by-bit boost with no error checking
;			14 enables transmit on all 3 phases (X10 boost, digital, & repeat)
;			13 selects delayed transmit to reduce potential for "dimmer flicker"
;			12 selects transmit at reduced power level (default for 3-phase xmit)
;			11 enables auto re-transmit of repeat following collision
;			10 selects abort transmission when collision is detected
;			 9 enables special handling when repeating bright/dim commands
;			 8 selects only one repeat for each command to prevent repeater ping-pong
;			 7 sets storm threshold 60/min. avg; OFF allows 30/min. avg.
;			 6 selects maximum sensitivity with AGC enabled
;			 5 selects high AGC threshold
;			 4 enables post X10 transmit AGC window
;			 3 enables pre zero-crossing AGC window
;			 2 ON immediately after ALL OFF reset selects mode housecode (9-8-2-2-ON)
;			 2 DIM reads back the detection threshold as pre-set dim (9-8-2-2-DIM)
;			 2 OFF anytime unlocks EEPROM for programming housecode disable
;			   (any command without the 9-8-2 preface will lock the EEPROM again)
;			 2 (ALL_OFF on selected housecode) disables housecode from being repeated
;			 2 (ALL_ON on selected housecode) enables housecode to be repeated
;			 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	cmdclr			; 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'00001110'		; to check for "9"
		btfsc	STATUS,Z		; skip if at least one digit was received
		goto	chkdgt			; check first digit
		movlw	b'00011010'		; to check for "8"
		decf	temp1,F			; was just one digit received?
		btfsc	STATUS,Z		; skip if not
		goto	chkdgt			; check second digit
		movlw	b'00011100'		; to check for "2"
		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 & check functions or mode select if so
		goto	cmdset			; check for on/off set function

		movlw	b'00001001'		; to check "DIM" for output AGC level
		xorwf	rkeycd,W		; is it "DIM"?
		btfsc	STATUS,Z		; skip if not "DIM"
		goto	outagc			; output AGC level
		movlw	b'00000001'		; to check "ALL_OFF" for disable housecode
		xorwf	rkeycd,W		; is it "ALL_OFF"?
		btfsc	STATUS,Z		; skip if not "ALL_OFF"
		goto	hsedis			; disable housecode
		movlw	b'00000011'		; to check "ALL_ON" for enable housecode
		xorwf	rkeycd,W		; is it "ALL_ON"?
		btfsc	STATUS,Z		; skip if not "ALL_ON"
		goto	hseena			; enable housecode
		movlw	b'00011111'		; to check for "Status Request" command
		xorwf	rkeycd,W		; is it "Status Request"?
		btfsc	STATUS,Z		; skip if not
		goto	hseack			; check and acknowledge mode status
		movlw	b'00000111'		; to check "OFF" to unlock housecode disable
		xorwf	rkeycd,W		; is it "OFF"?
		btfsc	STATUS,Z		; skip if not "OFF"
		goto	ulkdis			; unlock housecode disable

		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	moderr			; invalid code if not
		movfw	rkeycd			; recall key code (1-16)
		movwf	modsel			; save as mode select
		goto	nxtkey			; digit accepted & start timeout

cmdset	movlw	.240			; set mode command timeout for 4 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
		movfw	oldkey			; get last key pressed	

chkdgt	xorwf	rkeycd,W		; is it the correct digit?
		btfss	STATUS,Z		; skip if so
		goto	moderr			; indicate error if received incorrect digit
		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?
		andlw	b'00001111'		; mask all but housecode bits
		btfss	STATUS,Z		; skip if so
		goto	moderr			; reject command & indicate error 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

ulkdis	bsf		hsemod,mskulk	; set flag to unlock housecode disable
		goto	cmdclr			; clear unlock command

moderr	movfw	modcnt			; get number of correct digits received
		addlw	.254			; check if received at least 2 correct digits
		btfss	STATUS,C		; skip and indicate error if received at least 2 digits
		goto	cmdclr			; just flush the command if less than 2 correct digits
modeno	movlw	ledmod			; number of flashes for a mode command timeout
		call	ledset			; set LED count for number of flashes
cmdclr	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-15)

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
		btfsc	temp0,3			; skip if decimal value is 0-7
		goto	modset2			; set flag in mode byte 2 if 8-15
		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	modflg2,F		; set flag bit
		goto	savmod			; write new mode status into EEPROM
		
modset2	movlw	.249			; to add 249 decimal	
		addwf	temp0,F			; adjust 8-15 to 1-8 (sets carry)
modset3	rlf		temp1,F			; shift bit pointer left
		decfsz	temp0,F			; decrement and skip if bit pointer is correct
		goto	modset3			; loop until correct bit
		movfw	temp1			; get bit to set
		iorwf	modflg1,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
		btfsc	temp0,3			; skip if decimal value is 0-7
		goto	modclr2			; set flag in mode byte 2 if 8-15
		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	modflg2,F		; clear flag bit
		goto	savmod			; write new mode status into EEPROM
		
modclr2	movlw	.249			; to add 249 decimal	
		addwf	temp0,F			; adjust 8-15 to 1-8 (sets carry)
modclr3	rlf		temp1,F			; shift bit pointer left
		decfsz	temp0,F			; decrement and skip if bit pointer is correct
		goto	modclr3			; loop until correct bit
		movfw	temp1			; get bit to clear
		xorlw	b'11111111'		; flip all bits
		andwf	modflg1,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	modflg1			; get 1st bank of mode flags in RAM
		call	writee			; write new mode select into EEPROM

		incf	EEADR,F			; set next EEPROM address to read
		movfw	modflg2			; get 2nd bank of mode flags in RAM
		call	writee			; write new mode select into EEPROM

		incf	EEADR,F			; set next EEPROM address to read
		clrw					; to clear 1st housecode inhibit byte
		call	writee			; write into EEPROM

		incf	EEADR,F			; set next EEPROM address to read
		clrw					; to clear 2nd housecode inhibit byte
		call	writee			; write 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	modflg1,xmt3ph	; skip if 3-phase transmit is selected
		goto	nrmpwm			; set normal PWM width if not
lowpwm	movlw	.8				; for 2.08uS low power PWM pulsewidth
		goto	setpwm			; set PWM pulsewith
nrmpwm	movlw	.12				; 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	call	modsts			; check status of selected mode 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 CHECK STATUS OF SELECTED MODE BIT

;		Enter with modsel = X10 keycode for mode bit to check
;		Returns C=1 if mode is set

modsts	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	modsts1			; check flag in mode byte 2 if 8-15
		incf	temp0,F			; adjust 0-7 to 1-8
		movfw	modflg2			; get 2nd bank of mode flags
		goto	modsts2			; check bit status
		
modsts1	movlw	.249			; to add 249 decimal	
		addwf	temp0,F			; adjust 8-15 to 1-8
		movfw	modflg1			; get 1st bank of mode flags
modsts2	movwf	temp1			; save in temp workspace
modsts3	rrf		temp1,F			; shift LSB of workspace into C
		decfsz	temp0,F			; decrement and skip if C is correct status bit
		goto	modsts3			; loop until correct bit
		return


;		SUBROUTINE TO TRANSMIT AGC LEVEL AS PRE-SET DIM COMMAND

outagc	clrf	modcnt			; clear mode command digit count
		movfw	agcdac			; get 5-bit DAC AGC level
		btfss	PORTC,LOGAIN	; skip if at low gain
		goto	outhig			; output high gain AGC reading
		movwf	temp0			; save to divide by 4
		lsrf	temp0,F			; divide by 4
		lsrf	temp0,W
		andlw	b'00000111'		; make sure 5 MSBs are zero
		addlw	.24				; use values 25 to 31 for low gain
		goto	outdim			; output as pre-set dim command

outhig	addlw	.232			; check if DAC level is => 24 (gain switch is at 16)
		movfw	agcdac			; recall 5-bit DAC AGC level
		btfsc	STATUS,C		; skip if value is < 24
		movlw	.24				; set maximum value at low gain to 24

outdim	movwf	temp0			; save at temp register to reverse bits
		clrf	rhouse			; start with housecode cleared
		lsrf	temp0,F			; shift out LSB
		rlf		rhouse,F		; shift in LSB
		lsrf	temp0,F			; shift out 2nd bit
		rlf		rhouse,F		; shift in 2nd bit
		lsrf	temp0,F			; shift out 3rd bit
		rlf		rhouse,F		; shift in 3rd bit
		lsrf	temp0,F			; shift out 4th bit
		rlf		rhouse,F		; shift in 4th bit for pre-set dim "houscode"
		movlw	b'00010101'		; pre-set dim command for AGC levels 0-15
		btfsc	temp0,0			; skip if AGC level MSB = 0
		movlw	b'00010111'		; pre-set dim command for AGC levels 16-31
		movwf	xcmdcd			; save it to be transmitted
		bsf		xmtflg,xmtcmd	; set flag to transmit status
		return


;		SUBROUTE TO DISABLE REPEATING A SELECTED HOUSECODE

;		Enter with rhouse set to the housecode to disable

hsedis	btfss	hsemod,mskulk	; skip if housecode disable is unlocked
		goto	modeno			; indicate mode programming error
		movfw	rhouse			; get housecode
		movwf	temp0			; save in temporary workspace
		clrf	temp1			; clear bit pointer
		btfsc	temp0,3			; skip if decimal value is 0-7
		goto	hsedis2			; set disable flag in byte 2 if 8-15
		incf	temp0,F			; adjust 0-7 to 1-8
		bsf		STATUS,C		; set carry bit to shift 1 into LSB
hsedis1	rlf		temp1,F			; shift bit pointer left
		decfsz	temp0,F			; decrement and skip if bit pointer is correct
		goto	hsedis1			; loop until correct bit
		movfw	temp1			; get bit to set
		iorwf	hsemsk1,F		; set housecode disable bit
		goto	savhse			; write new housecode disable status into EEPROM
		
hsedis2	movlw	.249			; to add 249 decimal	
		addwf	temp0,F			; adjust 8-15 to 0-7 (sets carry)
hsedis3	rlf		temp1,F			; shift bit pointer left
		decfsz	temp0,F			; decrement and skip if bit pointer is correct
		goto	hsedis3			; loop until correct bit
		movfw	temp1			; get bit to set
		iorwf	hsemsk2,F		; set housecode disable bit
		goto	savhse			; write new housecode disable status into EEPROM


;		SUBROUTE TO ENABLE REPEATING A SELECTED HOUSECODE

;		Enter with rhouse set to the housecode to enable

hseena	btfss	hsemod,mskulk	; skip if housecode disable is unlocked
		goto	modeno			; indicate mode programming error
		movfw	rhouse			; get housecode
		movwf	temp0			; save in temporary workspace
		clrf	temp1			; clear bit pointer
		btfsc	temp0,3			; skip if decimal value is 0-7
		goto	hseena2			; set disable flag in byte 2 if 8-15
		incf	temp0,F			; adjust 0-7 to 1-8
		bsf		STATUS,C		; set carry bit to shift 1 into LSB
hseena1	rlf		temp1,F			; shift bit pointer left
		decfsz	temp0,F			; decrement and skip if bit pointer is correct
		goto	hseena1			; loop until correct bit
		movfw	temp1			; get bit to clear
		xorlw	b'11111111'		; flip all bits
		andwf	hsemsk1,F		; clear housecode disable bit
		goto	savhse			; write new housecode disable status into EEPROM
		
hseena2	movlw	.249			; to add 249 decimal	
		addwf	temp0,F			; adjust 8-15 to 0-7 (sets carry)
hseena3	rlf		temp1,F			; shift bit pointer left
		decfsz	temp0,F			; decrement and skip if bit pointer is correct
		goto	hseena3			; loop until correct bit
		movfw	temp1			; get bit to clear
		xorlw	b'11111111'		; flip all bits
		andwf	hsemsk2,F		; clear housecode disable bit

;		Write housecode repeat disable into EEPROM

savhse	bsf		xmtflg,xmtcmd	; set flag to transmit status
		clrf	modcnt			; clear mode command digit count

		banksel	EEADR			; select BANK3 to access EEPROM
		movlw	EERINH			; get housecode repeat inhibit address in EEPROM
		movwf	EEADR			; set EEPROM address to read
		movfw	hsemsk1			; get 1st bank of mode flags in RAM
		call	writee			; write new mode select into EEPROM

		incf	EEADR,F			; set next EEPROM address to read
		movfw	hsemsk2			; get 2nd bank of mode flags in RAM
		call	writee			; write new mode select into EEPROM

		clrf	BSR				; back to BANK0
		return


;		SUBROUTINE TO CHECK REPEAT DISABLE STATUS OF SELECTED HOUSECODE

;		Enter with rhouse = housecode to check
;		Returns C=1 if housecode repeat is disabled

msksts	movfw	rhouse			; get housecode
		movwf	temp0			; save in temporary workspace
		btfsc	temp0,3			; skip if decimal value is 0-7
		goto	msksts1			; check disable flag in byte 2 if 8-15
		incf	temp0,F			; adjust 0-7 to 1-8
		movfw	hsemsk1			; get housecode disable 2nd byte
		goto	msksts2			; check bit status
		
msksts1	movlw	.249			; to add 249 decimal	
		addwf	temp0,F			; adjust 8-15 to 1-8
		movfw	hsemsk2			; get housecode disable 1st byte
msksts2	movwf	temp1			; save in temp workspace
msksts3	rrf		temp1,F			; shift LSB of workspace into C
		decfsz	temp0,F			; decrement and skip if C is correct status bit
		goto	msksts3			; loop until correct bit
		return


;		SUBROUTINE TO REPORT DISABLE STATUS OF SELECTED HOUSECODE

;		Enter with modsel = X10 keycode for mode bit to check
;		Returns xcmdcd = X10 keycode for Status ON or Status OFF

hseack	call	msksts			; check mask status of selected housecode
		movlw	b'00011011'		; to acknowledge "Status ON"
		btfsc	STATUS,C		; skip if selected housecode is enabled
		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	rcvflg,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	rcvflg,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

;		Turn on 120KHz PWM transmitter

xmton
		banksel	CCP1CON			; select BANK5 for PWM control
		btfsc	PSTR1CON,STR1SYNC ; skip if PWM transmitter is not on yet
		goto	xmton2			; 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
	
		btfsc	stsflg,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	xmton1			; turn transmitter on
		btfsc	modflg1,colabt	; skip if no abort on a collision
		goto	xmton2			; return without turning transmitter on

xmton1
		banksel	PSTR1CON		; select BANK5 for PWM control
		movlw	.12				; for 3.12uS pulsewidth
		btfss	modflg1,xmt3ph	; skip & choose low power for 3-phase transmit
		btfsc	modflg1,xmtlow	; skip if not transmit at reduced power
		movlw	.8				; for 2.08us pulsewidth
		movwf	CCPR1L			; set PWM duty cycle

		movlw	b'00001100'		; to set PWM active high mode with single output
		btfsc	stsflg,pcbinv	; skip if PCB 1.22+ with positive PWM pulse
		movlw	b'00001111'		; to set PWM active low mode with single output
		movwf	CCP1CON			; set PWM mode
		clrf	PWM1CON			; no PWM delay between phases
		clrf	CCP1AS			; no PWM auto-shutdown
		movlw	b'00010001'		; to enable PWM P1A (pin 5) output at next PWM period
		btfss	stsflg,pcbv20	; skip if PCB 1.31 with firmware jumper at 1.20+
		movlw	b'00010010'		; to enable PWM P1B (pin 6) output at next PWM period
		movwf	PSTR1CON		; enable 120KHz PWM (STR1SYNC bit indicates PWM on)

xmton2	clrf	BSR				; back to BANK0
		btfsc	stsflg,pcbinv	; skip if PCB 1.22+ with positive PWM pulse
		bcf		PORTA,XMTINH	; make sure transmit inhibit is off for PCB 1.1 & 1.2
		return

;		Turn off 120KHz PWM transmitter

xmtoff
		banksel	PSTR1CON		; select BANK5 for PWM control
		clrf	PSTR1CON		; disable PWM output (also indicates xmtr is off)
		clrf	CCP1CON			; shut off PWM
		clrf	BSR				; back to BANK0

		btfsc	stsflg,pcbv20	; skip if not PCB 1.31 with firmware jumper at 1.20+
		bcf		PORTC,PWM120	; make sure PWM output pin 5 is a "0"
		btfss	stsflg,pcbinv	; skip if negative going PWM pulse
		bcf		PORTC,PWMOUT	; make sure PWM output pin 6 is a "0"
		btfsc	stsflg,pcbinv	; skip if positive going PWM pulse
		bsf		PORTC,PWMOUT	; make sure PWM output pin 6 is a "1"
		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	call	xmtoff			; make sure PWM is off and output is in correct state

;	bcf		PORTA,MODESW

;		Make sure PWM configuration is correct for this PCB

		banksel	TRISA			; select BANK1 for I/O direction control
		bsf		TRISA,X10120	; make sure comparator 1 output is disabled
		bcf		TRISC,PWMOUT	; make sure PWMOUT (pin 6) is an output
		bcf		TRISC,PWM120	; make sure PWM120 (pin 5) is an output

;		Make sure zero crossing interrupts are enabled

		banksel	TRISA			; select BANK1 for port direction register
		bsf		TRISA,ZCDIN		; set ZCD as an input
		banksel	IOCAF			; select BANK7 for interrupt on change
		movlw	b'00000001'		; 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

;		Set AGC threshold

		call	setagc			; set DAC AGC threshold for line input
		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

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

digdly	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
		if		ac50hz == 0		; if not for 50Hz AC powerline
		  addlw	.147			; .18 mS before the next zero crossing? (at 15.84MHz clk)
		else					; must be for 50Hz
		  addlw	.122			; .24 mS before the next zero crossing? (at 15.84MHz clk)
		endif
		btfss	STATUS,C		; skip and output digital data if so
		goto	digdly			; loop until just before ZCD

;	bsf		PORTA,MODESW

;		Send bit to digital I/O port early to accommodate Stargate

		bsf		PORTA,TW523O	; make sure TW523 output pulse is high
		btfss	xmtflg,xtbmsg	; skip if transmitting an internal message
		goto	ckrall			; check if receiving all bits
		btfsc	xmtflg,outbit	; skip if not transmitting a "1"
		bcf		PORTA,TW523O	; set output pulse low for a "1"
		goto	main01

ckrall	btfsc	modflg2,allbit	; skip if not receiving all bits
		goto	main01			; no early digital output when receiving all bits

		movfw	outcnt			; get digital output bit count
		btfsc	STATUS,Z		; skip if a message is being output
		goto	main01			; no output
		btfsc	rcvflg,rcvbit	; skip if received bit is a "0"
		bcf		PORTA,TW523O	; set output pulse low for a "1"
		decf	outcnt,F		; decrement bit count

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	.20				; check for timer preload (239)
		btfsc	STATUS,C		; skip if not
		goto	main02			; begin processing next half cycle if so
		addlw	.80				; past next transmit window with no zero crossing?
		btfss	STATUS,C		; skip if so
		goto	main01			; loop while waiting for zero crossing
		goto	main00			; stop & disable transmitter if no zero crossing

;		Make sure comparators and D/A converter are enabled

main02	movf	ledcnt,F		; check if LED is being flashed
		btfss	STATUS,Z		; skip if not
		goto	main05			; flashing overrides normal LED function
		movfw	modcnt			; get number of "982" mode command digits accepted
		addlw	.252			; check if received mode select (C=1 if so)
		btfss	STATUS,C		; skip & check mode status if so
		goto	main03			; turn on LED if not
		btfss	modtim,7		; skip & check mode if mode timer is in first 2 seconds
		goto	main04			; just turn on LED if not
		call	modsts			; check mode status of this bit
		btfss	STATUS,C		; skip and turn LED on if a "1"
		goto	main05			; leave LED off if a "0"
		goto	main04			; turn LED on even if not locked
main03	btfss	xmtflg,xmtmsg	; turn LED on if transmitting
		btfsc	stsflg,freqok	; skip and leave LED off if not locked
main04	bsf		PORTC,LEDOUT	; turn on LED

main05	movfw	agcdac			; get AGC DAC threshold
		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
		movwf	DACCON0			; make sure D/A converter is enabled
		movlw	b'10001000'		; enable comparator fixed voltage reference set for 2V
		movwf	FVRCON
		movlw	b'10000100'		; enable comparator 1 to monitor X10 Boost input
		movwf	CM1CON0
		movlw	b'10100000'		; positive transition, fixed reference, use C12IN0- input 
		movwf	CM1CON1
		movlw	b'10110100'		; enable comparator 2 to monitor X10 BPF input with test point
		btfsc	stsflg,modjmp	; skip if mode jumper is installed
		btfss	stsflg,pcbv20	; skip if PCB 1.31(+) firmware jumper at 1.2+ (PWM on pin 5)
		movlw	b'10000100'		; enable comparator 2 to monitor X10 BPF input with no output
		movwf	CM2CON0			; set comparator 2 configuration
		movlw	b'10010001'		; positive transition, DAC reference, use C12IN1- input 
		movwf	CM2CON1

		banksel	PSTR1CON		; select BANK5 for PWM control
		btfsc	PSTR1CON,STR1SYNC ; skip if PWM transmitter is off
		goto	clrwin			; just sample pulses if transmitter is on
		clrf	BSR				; back to BANK0

		btfsc	xmtflg,xtbmsg	; skip if not transmitting an internal message
		goto	dlywin			; no pre-window source check when transmitting if so
		btfsc	xmtflg,rptmsg	; skip if not repeating a message
		goto	dlywin			; 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	rcvflg,W		; check all receive flags
		btfsc	STATUS,Z		; skip if a X10 command is already being processed
		goto	main06			; check for a new X10 command prior to reception window

		btfsc	rcvflg,dgioin	; skip if not already receiving a digital input
		goto	chkdio			; check for next digital input bit

		btfsc	rcvflg,bx10in	; skip if not receiving an X10 Boost input
		goto	main06			; check early window for X10 Boost input
		btfsc	modflg2,allbit	; skip if mode option "receiving all bits" is not enabled
		goto	main06			; check early window when receiving all bits
		btfsc	rcvflg,dimcmd	; skip if not receiving a dim command
		goto	main06			; check early window for dim repeat
		btfss	rcvflg,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

;		Monitor digital port until 190uS after zero crossing to begin pre-window check

main06	btfss	T1CON,TMR1ON	; skip if Timer 1 is running
		goto	main			; stop and wait for next zero crossing if not

chkerl	btfss	PORTA,TW523I	; skip if TW523 input is not asserted (logic "0")
		goto	dxmton			; set digital input flag & turn on transmitter
		movfw	TMR1H			; get Timer 1 high byte
		addlw	.15				; is it 190uS after the zero crossing?
		btfss	STATUS,C		; skip if so
		goto	chkerl			; loop if not

;		Check for any X10 command in pre-reception window

		movlw	.4				; recognize X10 Boost input at 4 pulses
		movwf	bstcnt			; set X10 Boost pulse count threshold
		clrf	lincnt			; clear powerline input pulse count
		bcf		PIR2,C1IF		; clear X10 Boost pulse flag
		bcf		PIR2,C2IF		; clear powerline input pulse flag

	bcf		PORTA,MODESW	; turn diagnostic pulse on

;		Count any pulses and monitor digital port from 190us to 260uS

erlylp	movfw	TMR1H			; get Timer 1 high byte
		addlw	.14				; past the pre-window check?
		btfsc	STATUS,C		; continue checking for pre-window pulses if not 
		goto	erly12			; done with early window check

		btfss	PORTA,TW523I	; skip if TW523 input is not asserted (logic "0")
		goto	dxmton			; set digital input flag & turn on transmitter
		btfss	PIR2,C2IF		; skip if a pulse was detected on the X10 powerline input
		goto	erly11			; check the X10 Boost input
		bcf		PIR2,C2IF		; clear the X10 powerline input flag bit
		incf	lincnt,F		; increment powerline input pulse count
erly11	btfss	PIR2,C1IF		; skip if a pulse was detected on the X10 Boost input
		goto	erlylp			; loop and check for end of pre-window if not
		bcf		PIR2,C1IF		; clear the X10 Boost input flag bit
		decfsz	bstcnt,F		; count off X10 Boost pulse & skip if enough to respond
		goto	erlylp			; loop and check for end of pre-window if not

		bsf		rcvflg,bx10in	; indicate receiving an X10 Boost input
		call	xmton			; turn transmitter on
		btfsc	modflg2,allbit	; skip if mode option "receiving all bits" is not enabled
		bcf		PORTA,TW523O	; set output pulse low for a "1"

erly12

	btfsc	stsflg,pcbinv	; skip if not inverted output with XMTINH
	btfss	xmtflg,outbit	; skip if transmitter is on for this half cycle
	bsf		PORTA,MODESW	; turn diagnostic pulse off

		btfsc	rcvflg,bx10in	; skip if not receiving an X10 Boost input
		goto	dlywin			; delay to sample X10 reception window

		movfw	lincnt			; get number of X10 line pulses in pre-window
		addlw	.247			; check if >8 pulses (>131KHz)
		btfsc	STATUS,C		; skip if < 131KHz
		goto	dlywin			; not a 120KHz X10 signal burst if > 131KHz
		addlw	.2				; check if <7 pulses (<100KHz)
		btfss	STATUS,C		; skip if > 100KHz
		goto	dlywin			; not a 120KHz X10 signal burst if < 100KHz

		btfsc	modflg2,allbit	; skip if mode option "receiving all bits" is not enabled
		bcf		PORTA,TW523O	; set output pulse low for a "1"
		btfsc	rcvflg,dimcmd	; skip if not receiving a dim command
		goto	dimrpt			; turn on transmitter & set up dim repeat
		btfsc	rcvflg,xtdcmd	; skip if not receiving an extended command
		goto	xtdrpt			; turn on transmitter & set repeat of extended command
		goto	dlywin			; no early transmit if not dim or extended command

;		Set up repeat of dim command

dimrpt	bcf		PORTA,TW523O	; set output pulse low for a "1"
		call	xmton			; turn transmitter on
		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	dlywin			; begin sampling powerline during reception window

;		Set up repeat of extended command

xtdrpt	call	xmton			; turn transmitter on
		movfw	rcvcnt			; get received bit count
		addlw	.194			; check for end of first half of extended message 62+194=256
		btfss	STATUS,Z		; skip if at start bit for second half of doublet
		goto	dlywin			; no repeat until after first half of extended command

		btfss	modflg2,onerpt	; skip if no repeat of already repeated message
		bsf		xmtflg,rptmsg	; set flag to indicate a repeat is in process
		movlw	.62				; set 62-bit extended message length
		movwf	xmtcnt			; set number of bits to transmit
		goto	dlywin			; begin sampling powerline during reception window

;		When receiving a digital input, check digital port until beginning of reception window

chkdio	movfw	TMR1H			; get Timer 1 high byte
		addlw	.14				; is it 260uS past the zero crossing?
		btfsc	STATUS,C		; skip and continue checking digital input if not
		goto	clrwin			; sample powerline to check for possible collision
		btfsc	PORTA,TW523I	; skip if TW523 input is ON (transmit a logic "1")
		goto	chkdio			; loop until beginning of X10 reception window

dxmton	bsf		rcvflg,dgioin	; indicate receiving a digital input
		bsf		xmtflg,outbit	; set flag indicating a "1" will be transmitted
		btfsc	modflg2,allbit	; skip if mode option "receiving all bits" is not enabled
		bcf		PORTA,TW523O	; set output pulse low for a "1"
		btfss	modflg1,xmtdly	; skip if transmit burst should be delayed
		call	xmton			; turn transmitter on

;		Delay until after 260uS for the beginning of X10 reception window

dlywin	movfw	TMR1H			; get Timer 1 high byte
		addlw	.14				; at beginning of X10 reception window?
		btfss	STATUS,C		; skip if so
		goto	dlywin			; wait for beginning of reception window

;		Turn on transmitter in delayed transmit mode

		btfsc	xmtflg,outbit	; skip if output bit is zero
		call	xmton			; make sure the transmitter is on

;		Start X10 reception window

clrwin	clrf	BSR				; make sure BANK0 is selected
		clrf	cyccnt			; clear cycle count for next reception window
		bcf		PIR2,C2IF		; make sure the flag bit is cleared

	bcf		PORTA,MODESW	; turn diagnostic pulse on

;		Count pulses until end of reception window 930uS after zero crossing

main10	movfw	TMR1H			; get Timer 1 high byte
		addlw	.2				; past the X10 reception window?
		btfsc	STATUS,C		; skip and monitor X10 reception window if not
		goto	main12			; finished with reception window

		btfss	PIR2,C2IF		; skip if a pulse is detected on the powerline input
		goto	main11			; check digital port if not
		bcf		PIR2,C2IF		; make sure the flag bit is cleared
		incf	cyccnt,F		; increment cycle count
		btfsc	STATUS,Z		; skip if no overflow
		decf	cyccnt,F		; set max cycle count = 255

main11	btfsc	PORTA,TW523I	; skip if TW523 input is asserted (logic "1")
		goto	main10			; loop for reception window

		movlw	.64				; to set cycle count to 64
		movwf	cyccnt			; set cycle count to 64 to force receiving a "1"

main12

	btfsc	stsflg,pcbinv	; skip if not inverted output with XMTINH
	btfss	xmtflg,outbit	; skip if transmitter is on for this half cycle
	bsf		PORTA,MODESW	; turn diagnostic pulse off

;		Delay until timer overflow (end of X10 transmit window)

main15	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	main15			; loop until timer overflow

;		Turn off transmitter and digital output at end of X10 window

		call	xmtoff			; turn off the transmitter (also indicates xmtr is off)
		btfss	modflg2,allbit	; skip if receiving all bits
		bsf		PORTA,TW523O	; make sure TW523 output pulse is high

;	movfw	lincnt			; get early-window powerline pulse count
;	movfw	cyccnt
;	bsf		PORTC,AGCTST
;	call	tstpls			; output powerline pulse count to diagnostic test point
;	bcf		PORTC,AGCTST

	movfw	agcdac			; get AGC DAC level
	call	agcpls			; output AGC level as pulses to AGC test point

		movf	ledcnt,F		; check if LED is being flashed
		btfss	STATUS,Z		; skip if not
		goto	main17			; leave LED status alone when flashing
		btfsc	xmtflg,xmtmsg	; skip if not transmitting a message
		goto	main17			; leave LED on when transmitting a message
		btfsc	rcvflg,inpbit	; skip if received bit was a "0"
		goto	main17			; leave LED on for a "1"
		movfw	modcnt			; get number of "982" mode command digits accepted
		addlw	.252			; check if received mode select (C=1 if so)
		btfsc	STATUS,C		; skip & turn on LED if not
		btfss	modtim,7		; skip & check mode if mode count timer is in first 2 seconds
		goto	main16			; just turn off LED if not
		call	modsts			; check mode status of this bit
		btfss	STATUS,C		; skip if bit is a "1"
main16	bcf		PORTC,LEDOUT	; turn off LED for dim illumination

;		Check AGC sample point

main17 	btfss	modflg2,pstagc	; skip if post X10 transmit AGC window is enabled
		goto	main20			; check for 3-phase transmission

;		Sample AGC 1.36 mS to 2.13 mS after zero crossing

		btfss	rcvflg,linein	; skip if receiving a powerline input
		goto	main18			; run AGC check when not receiving an X10 command
		btfss	rcvflg,rcverr	; skip & run AGC check if error receiving X10 command
		goto	main20			; no AGC check if receiving valid X10 command

main18	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	.252			; is it 1.25 mS after the last zero crossing (at 15.84MHz clk)
		btfss	STATUS,C		; skip to begin AGC sample if so
		goto	main18			; loop if not
		movlw	.240			; sample AGC until 2.13 mS after the last zero crossing
		movwf	agcend			; set up AGC cycle end

	bcf		PORTA,MODESW	; turn diagnostic pulse on

		call	agcloop			; run AGC loop

	bsf		PORTA,MODESW	; turn diagnostic pulse off

;		Check if 3-phase transmission is enabled

main20	btfss	modflg1,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
		if		ac50hz == 0		; if not for 50Hz AC powerline
		  addlw	.230			; past the start of the 2nd 3-phase window?
		else					; must be for 50Hz
		  addlw	.227			; past the start of the 2nc 3-phase window?
		endif
		btfss	STATUS,C		; skip if so
		goto	main22			; loop until the beginning of the window

	bcf		PORTA,MODESW	; turn diagnostic pulse on

		btfsc	xmtflg,outbit	; skip if output bit is "0"
		call	xmton			; turn transmitter on

main23	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
		if		ac50hz == 0		; if not for 50Hz AC powerline
		  addlw	.215			; past the end of the 2nd 3-phase window?
		else					; must be for 50Hz
		  addlw	.212			; past the end of the 2nd 3-phase window?
		endif
		btfss	STATUS,C		; skip if so
		goto	main23			; loop until end of the window

	btfsc	stsflg,pcbinv	; skip if not inverted output with XMTINH
	btfss	xmtflg,outbit	; skip if transmitter is on for this half cycle
	bsf		PORTA,MODESW	; 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
		if		ac50hz == 0		; if not for 50Hz AC powerline
		  addlw	.187			; past the start of the 3rd 3-phase window?
		else					; must be for 50Hz
		  addlw	.169			; past the start of the 3rd 3-phase window?
		endif
		btfss	STATUS,C		; skip if so
		goto	main32			; loop until the beginning of the window

	bcf		PORTA,MODESW	; turn diagnostic pulse on

		btfsc	xmtflg,outbit	; skip if output bit is "0"
		call	xmton			; turn transmitter on

main33	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
		if		ac50hz == 0		; if not for 50Hz AC powerline
		  addlw	.172			; past the end of the 3rd 3-phase window?
		else					; must be for 50Hz
		  addlw	.154			; past the end of the 3rd 3-phase window?
		endif
		btfss	STATUS,C		; skip if so
		goto	main33			; loop until end of the window

	btfsc	stsflg,pcbinv	; skip if not inverted output with XMTINH
	btfss	xmtflg,outbit	; skip if transmitter is on for this half cycle
	bsf		PORTA,MODESW	; 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

;	btfsc	rcvflg,dimcmd	; FOR TESTING ONLY
;	btfsc	xmtflg,xtbmsg	; FOR TESTING ONLY
;	btfsc	xmtflg,rptmsg	; FOR TESTING ONLY
;	bcf		PORTA,TW523O	; FOR TESTING ONLY

		btfsc	modflg2,preagc	; skip if pre zero-crossing AGC window is disabled
		btfsc	rcvflg,linein	; skip if not receiving a powerline input (RR501 problem)
		goto	main			; loop and wait for next zero crossing

main41

		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
			if		ac50hz == 0		; if not for 50Hz AC powerline
			  addlw	.161		; 1.08 mS before the next zero crossing? (15.84MHz)
			else				; must be for 50Hz
			  addlw	.136		; 1.14 mS before the next zero crossing? (15.84MHz)
			endif
		btfss	STATUS,C		; skip to begin AGC sample if so
		goto	main41			; loop if not

			if		ac50hz == 0		; if not for 50Hz AC powerline
			  movlw	.149		; sample AGC until .30mS before next zero crossing (15.84MHz)
			else				; must be for 50Hz
			  movlw	.124		; sample AGC until .36mS before next zero crossing (15.84MHz)
			endif
		movwf	agcend			; set up AGC cycle end

	bcf		PORTA,MODESW	; turn diagnostic pulse on

		call	agcloop			; run AGC loop

	bsf		PORTA,MODESW	; turn diagnostic pulse off

		goto	main			; loop and wait for next zero crossing


;		AGC LOOP

;		Uses internal comparator reference to measure powerline noise

agcloop decf	agclvl,F		; decrease AGC level by 1 count for next AGC sample
		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
		movwf	DACCON0			; make sure D/A converter is enabled
		movlw	b'10110100'		; enable comparator 2 to monitor X10 BPF input with test point
		btfsc	stsflg,modjmp	; skip if mode jumper is installed
		btfss	stsflg,pcbv20	; skip if PCB 1.31(+) firmware jumper at 1.2+ (PWM on pin 5)
		movlw	b'10000100'		; enable comparator 2 to monitor X10 BPF input with no output
		movwf	CM2CON0			; set comparator 2 configuration
		movlw	b'10010001'		; positive transition, DAC reference, use C12IN1- input 
		movwf	CM2CON1

;		Measure powerline noise until end of AGC sample window

agclp0	clrf	BSR				; make sure BANK0 is selected
		movlw	.8				; allow 8 noise pulses per AGC increment
		movwf	temp1			; initialize noise pulse count
agclp1	bcf		PIR2,C2IF		; make sure the flag bit is cleared
agclp2	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
		addwf	agcend,W		; check for end of AGC window
		btfsc	STATUS,C		; continue AGC sample window if not
		return					; end AGC sample window
		
;		Count noise pulses

		btfss	PIR2,C2IF		; skip if a pulse is detected on the powerline input
		goto	agclp2			; loop if not
		decfsz	temp1,F			; count off one noise pulse
		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
		goto	agclp0			; start another 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 4.0V
		movlw	.204			; for maximum AGC level of 4.0V
		btfsc	STATUS,C		; skip if < 4.0V
		movwf	agclvl			; set AGC to 4.0V maximum
		return

;		Set AGC DAC & increase AGC level by 1.5 if high threshold

setagc	lsrf	agclvl,W		; get current 1/2 AGC level
		movwf	agcdac			; save at AGC DAC threshold
		lsrf	agcdac,F		; divide by 2
		lsrf	agcdac,F		; divide by 2 again for a total of 8
		movfw	agcdac			; W = 1/8 AGC level
		lsrf	agcdac,F		; agcdac = 1/16 AGC level (for 1.5x)
		btfss	modflg2,thldhi	; skip if high threshold
		clrf	agcdac			; use background level if not high threshold
		incf	agcdac			; ensure margin above background noise level
		addwf	agcdac,F		; AGCDAC = (1 or 1.5 x background) +1
		movfw	agcdac			; get AGC DAC threshold
		addlw	.231			; check if < 4.0V
		movlw	.25				; for maximum AGC level of 4.0V
		btfsc	STATUS,C		; skip if < 4.0V
		movwf	agcdac			; set maximum AGC DAC threshold to 4.0V
		return					; done with AGC loop


;		Diagnostic to output a series of pulses to TP1 equal to the value passed in W

tstpls	btfss	stsflg,modjmp	; skip if mode jumper is not installed
		return					; no diagnostic pulses when jumper is installed

		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	bcf		PORTA,MODESW	; output a negative going pulse
		bsf		PORTA,MODESW
		decfsz	temp2,F			; done?
		goto	tstplp			; loop if not
		return

;		Diagnostic to output a series of pulses to AGCTST to indicate the AGC threshold

agcpls	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

agcplp	bsf		PORTC,AGCTST	; output a positive going pulse
		bcf		PORTC,AGCTST
		decfsz	temp2,F			; done?
		goto	agcplp			; loop if not
		return


;		EEPROM DATA

EE		CODE	0xF000			; (location found by trial and error)
		DE		"XTB-IIR_V1.25"	; program name and version
		if		ac50hz == .0	; if not for 50Hz AC powerline
		  DE	"   "			; standard 60Hz version
		else					; must be for 50Hz
		  DE	"E  "			; 50Hz version
		endif

		CODE	0xF010
		DE		moddef1			; EEPROM storage for first mode flag byte
		DE		moddef2			; EEPROM storage for second mode flag byte
		DE		mskdef1			; EEPROM storage for first housecode repeat disable byte
		DE		mskdef2			; EEPROM storage for second housecode repeat disable byte
		DE		hsedef			; EEPROM storage for selected housecode and PCB select

		END						; directive 'end of program'
