;********************************************************************
;																	*
;	XTB Serial Powerline Interface									*
;																	*
;	This program is for the XTB RS232 Serial Interface (XTB-232)	*
;																	*
;********************************************************************
;																	*
;	Filename:		XTB-232.asm										*
; 	Date:			04/03/14										*
;	File Version:	rev 1.05/E										*
;																	*
;	Author:			Jeff Volp										*
;	Company:		JV Digital Engineering 							*
;																	*
;	This code is Copyright JV Digital Engineering 04/03/14			*
;																	*
;	Version B3:														*
;		added ability to accept Preset Dim & Extended status report	*
;		added polite transmission mode with 5 auto-retransmits		*
;	Version B4:														*
;		added "doublet" extended code transmissions					*
;		polite auto-retransmit is reduced to 1						*
;	Version B5:														*
;		adds delay before sending output request to queue commands	*
;		processes each half of received doublet independently		*
;		issues "A5" clock initialization request at power-up		*
;	Version B6:														*
;		no check for powerline clear during multi-dim sequence		*
;		adjust receive buffer to do up to 8-byte receive transfers	*
;		flash LED 5 times for serial port timeout					*
;		also fixed bug that could cause a hang due to collisions	*
;	Version 1.00/E (Initial Release)								*
;		same as B6 except source was cleaned up & 50Hz option added	*
;	Version 1.01/E													*
;		Ring Indicator polarity is reversed to match CM11A			*
;	Version 1.02/E													*
;		Fix possible bug in XMTON if LED is being flashed			*
;		Lock ZCD state after each zero crossing (aesthetics only)	*
;		Assert the ring indicator for just 1 second after power on	*
;	Version 1.03/E													*
;		No noise sample when receiving X10 to ignore 2mS XPCR pulse *
;	Version 1.04/E													*
;		Improve AGC response to random "X10 like" signal bursts		*
;	Version 1.04a/E  (7/29/13)										*
;		Correctly checks for errors receiving an extended command	*
;	Version 1.05/E (4/03/14)										*
;		AGC modified to raise threshold for errors or transients	*
;		Make code more like XTB-523 where possible					*
;																	*
;********************************************************************

	list      p=16F1823			; list directive to define processor
	#include <p16F1823.inc>		; processor specific variable definitions

	errorlevel  -302			; suppress message 302 from list file


    __CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_ON & _PWRTE_ON & _MCLRE_OFF & _CP_ON & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
    __CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_ON & _BORV_25 & _LVP_OFF

; I/O PIN DEFINITIONS

ZCDIN	EQU		H'05'			; (RA5) Zero Crossing Detector - 50/60Hz reference input
TP120K	EQU		H'04'			; (RA4) 120KHz PWM 120KHz reference test point
ICSVPP	EQU		H'03'			; (RA3) Programmer Master Clear / Vpp Input
TPX10	EQU		H'02'			; (RA2) X10 Input test point
ICSCLK	EQU		H'01'			; (RA1) Programmer Clock In (also serial timeout disable)
LEDGRN	EQU		H'00'			; (RA0) Programmer Data In, LED: 1 green, 0 red, ("0" for timeout enable)
SER_RX	EQU		H'05'			; (RC5) Serial Data In - 1 for opto-isolator LED ON (0 input)
SER_TX	EQU		H'04'			; (RC4) Serial Data Out - 0 for opto-isolator ON (high output)
PWM120	EQU		H'03'			; (RC3) 120KHz PWM output - directly drives power stage
RNGOUT	EQU		H'02'			; (RC2) Serial Ring Out - 0 for opto-isolator ON (high output)
X10BPF	EQU		H'01'			; (RC1)	120KHz X10 input from powerline bandpass amplifier
LEDRED	EQU		H'00'			; (RC0) LED: 1 red, 0 green

; SPECIAL REGISTER EQUATES

GO		EQU		H'01'			; ADCCON0 GO/DONE bit

; VARIABLE DEFINITIONS

			UDATA		0x20
rambgn
periodH		RES		.1			; period measurement MSB
periodL		RES		.1			; period measurement LSB
frqerr		RES		.1			; 2's compliment frequency error (0 for no error)
seccnt		RES		.1			; second timer - counts 60 to 1 each second
timout		RES		.1			; next command timeout - maximum time between serial inputs
cyccnt		RES		.1			; received bit cycle count
onezer		RES		.1			; net total of one versus zero bits (error if not 1-3)
onecnt		RES		.1			; received "1" count
gapcnt		RES		.1			; received gap length (255 max)
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
funcod		RES		.1			; unpacked function code (5-bit X10 code)
dimcnt		RES		.1			; number of dim/bright commands to be transmitted
dimlvl		RES		.1			; dim level
rcvcnt		RES		.1			; received X10 bit count
rcvsrg		RES		.8			; received X10 data shift register
xmtcnt		RES		.1			; transmit X10 bit count
xmtagn		RES		.1			; re-transmit count
xmtsrg		RES		.8			; transmit X10 data shift register
dcdbuf		RES		.5			; received X10 command decode buffer
rcvptr		RES		.1			; serial port receive buffer pointer
rcvbuf		RES		.5			; serial port receive 5-byte buffer
dblbuf		RES		.5			; serial port receive 5-byte double buffer (for collisions)
xmtptr		RES		.1			; serial port transmit buffer pointer
xmtbuf		RES		.10			; serial port transmit 10-byte buffer
reqdly		RES		.1			; delay before issuing "5A" serial port output request
chksum		RES		.1			; checksum for received serial message
agclvl		RES		.1			; AGC level (8x the value used for the D/A)
agcend		RES		.1			; end of AGC sample window
dlycnt		RES		.1			; delay loop counter
ramend

			UDATA_SHR	0x70

nxttune		RES		.1			; next setting for OSCTUNE
sertmp		RES		.1			; global general purpose register (serial temp)
sercnt		RES		.1			; global general purpose register (serial count)
temp0		RES		.1			; global general purpose register
temp1		RES		.1			; global general purpose register
temp2		RES		.1			; global general purpose register (for AGC loop)

stsflg		RES		.1			; general status flags

rcverr		EQU		H'07'		; "1" indicates an error was detected in the data pattern
rcvpnd		EQU		H'06'		; "1" indicates received X10 command is pending transfer
xtdcmd		EQU		H'05'		; "1" indicates receiving an extended command
colisn		EQU		H'04'		; "1" indicates a collision was detected
xmtmsg		EQU		H'03'		; "1" indicates transmitter is active (from 1st start bit to 6-bit gap)
outbit		EQU		H'02'		; "1" indicates next bit to be transmitted is a logic "1"
dimcmd		EQU		H'01'		; "1" indicates received a bright/dim command
dnload		EQU		H'00'		; "1" indicates receiving download

serflg		RES		.1			; serial port I/O status flags

rcvrdy		EQU		H'07'		; "1" indicates the XTB is ready to receive data ("55" was sent)
rcving		EQU		H'06'		; "1" indicates serial data is being received (set by header byte)
chkout		EQU		H'05'		; "1" indicates serial checksum sent, waiting for "00" acknowledgement
srdy55		EQU		H'04'		; "1" indicates send "55" serial ready	
serreq		EQU		H'03'		; "1" indicates send "5A" output request every second until acknowledged
serxmt		EQU		H'02'		; "1" indicates a byte is queued and ready to transmitted
rcvful		EQU		H'01'		; "1" indicates the serial input buffer is full (2 bytes or 5 bytes)
xmtful		EQU		H'00'		; "1" indicates the serial output buffer ready to be transmitted

serflg2		RES		.1			; additional serial port status flags

clkset		EQU		H'07'		; "1" indicates the startup sequence has been completed
clkpnd		EQU		H'06'		; "1" indicates the startup sequence is pending
touten		EQU		H'05'		; "1" indicates serial port timeout is enabled
rngena		EQU		H'04'		; "1" indicates serial ring indicator is enabled
srepnd		EQU		H'03'		; "1" indicates serial ring enable pending
srdpnd		EQU		H'02'		; "1" indicates serial ring disable pending
strtup		EQU		H'01'		; "1" indicates power-up first second startup for ring indicator pulse

;			Error Indications

ledser		equ		.10			; 5 LED flashes for a serial link error
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

;	******	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


;		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
		btfsc	PIR1,TMR1IF		; skip if not a Timer 1 overflow
		goto	timint			; handle timer interrupt if so
		banksel	IOCAF			; select IOC control bank
		btfss	IOCAF,IOCAP5	; skip if it was a zero crossing interrupt
		goto	unknwn			; unidentified interrupt if not

;		Calculate line frequency period by adding each half cycle

		banksel	IOCAF			; select BANK7 for interrupt on change
		bcf		IOCAF,IOCAF5	; clear interrupt flag
		clrf	BSR				; back to Bank0
		bcf		INTCON,IOCIE	; disable ZCD interrupt on change
		bcf		T1CON,TMR1ON	; stop Timer 1
		movfw	TMR1L			; get Timer 1 LSB
		addwf	periodL,F		; add to last half cycle LSB
		btfsc	STATUS,C		; skip if carry clear
		incf	periodH,F		; propagate carry into MSB
		movfw	TMR1H			; get Timer 1 MSB
		addwf	periodH,F		; add to last half cycle MSB
		movlw	.247			; preset Timer 1 to -1.11 mS (at 8.16MHz clk)
		movwf	TMR1H			; set high byte
		movlw	.35				; adjust for period 66.4 MSB counts
		movwf	TMR1L			; set low byte
		bsf		T1CON,TMR1ON	; restart Timer 1

;		Clear watchdog timer, lock the ZCD to the present state

		clrwdt					; clear watchdog timer
		movlw	.10				; for a 15uS delay 
		movwf	temp1
intdly	decfsz	temp1,F			; tick off one loop @ 1.5uS per loop
		goto	intdly			; delay 15uS for stable sample of ZCD input level

		btfsc	PORTA,ZCDIN		; skip if falling edge of AC input
		goto	zcdhi

		bcf		PORTA,ZCDIN		; to set ZCD output low
		banksel	TRISA			; select BANK1 for port direction register
		bcf		TRISA,ZCDIN		; set ZCD as an output
		clrf	BSR				; back to BANK0
		goto	chkfls			; check for LED flashing
	
zcdhi	bsf		PORTA,ZCDIN		; to set ZCD output high
		banksel	TRISA			; select BANK1 for port direction register
		bcf		TRISA,ZCDIN		; set ZCD as an output
		clrf	BSR				; back to BANK0

;		Turn off LED if not flashing & check timeout status

chkfls	movf	ledcnt,F		; check if LED is being flashed
		btfss	STATUS,Z		; skip and check timeout enable if LED not flashing
		goto	xmtnxt			; output next bit when transmitting
		bcf		PORTA,LEDGRN	; turn off green LED and set timeout jumper to "0"
		btfsc	PORTA,ICSCLK	; skip if serial ICSCLK is jumpered to ISCDATA
		bsf		serflg2,touten	; enable serial port timeout
		btfss	PORTA,ICSCLK	; skip if serial ICSCLK is not jumpered to ISCDATA
		bcf		serflg2,touten	; disable serial port timeout

;		Turn on transmitter as required & set LED to red or green as appropriate

xmtnxt	btfss	stsflg,xmtmsg	; skip if transmitting a message
		goto	setgrn			; turn on green LED when not transmitting
		btfss	stsflg,outbit	; skip & transmit if output bit is a "1"
		goto	setgrn			; turn on green LED when transmitting a "0"
		call	xmton			; turn on transmitter
		movf	ledcnt,F		; check if LED is being flashed
		btfss	STATUS,Z		; skip and turn on red LED for "1" when transmitting
		goto	chkpol			; leave LED status alone when flashing
		bsf		PORTC,LEDRED	; turn on red LED
		bcf		PORTA,LEDGRN
		goto	chkpol			; next

setgrn	movf	ledcnt,F		; check if LED is being flashed
		btfss	STATUS,Z		; skip and turn on green LED
		goto	chkpol			; leave LED status alone when flashing
		bsf		PORTA,LEDGRN	; turn on green LED
		bcf		PORTC,LEDRED

;		Determine which AC input zero crossing just passed

chkpol	btfsc	PORTA,ZCDIN		; skip if falling edge of AC input
		goto	adjclk			; adjust clock frequency on rising edge

;		Check timeout waiting for clock initialization checksum acknowledgement

		movf	timout,F		; check if timeout is already zero (no timeout)
		btfsc	STATUS,Z		; skip & check for command timeout if not
		goto	onesec			; no timeout check
		decfsz	timout,F		; count down timeout & skip if exceeded
		goto	onesec			; no timeout if not
		btfss	serflg2,clkpnd	; skip if clock initialization is pending
		goto	chktmo			; check for normal serial port timeout

		bcf		serflg2,clkpnd	; clear clock initialization pending flag
		bsf		serflg2,clkset	; set clock initialization completed flag
		bcf		serflg,chkout	; done waiting for checksum
		bcf		stsflg,dnload	; make sure download flag is cleared
		bsf		serflg,rcvrdy	; set serial receive ready flag
		bsf		serflg,srdy55	; set flag to send "55" serial port ready
		bsf		serflg,serxmt	; set flag to transmit "55" ready
		movlw	.1				; to flash LED once
		movwf	ledcnt			; set LED flash count
		goto	adjclk			; done with initialization

;		Check serial input timeout & flush serial input buffer if exceeded

chktmo	btfss	serflg2,touten	; skip if serial port timeout is enabled (no jumper to zero)
		goto	onesec			; no timeout check if not
		call	serrst			; reset serial port & indicate serial error

;		Count 60Hz to transmit startup poll once a second

onesec	decfsz	seccnt,F		; tick off one cycle
		goto	intrtn			; done if not zero
		btfsc	serflg2,strtup	; skip if it is the initialization cycle
		bsf		PORTC,RNGOUT	; deassert serial ring out (low)
nxtsec	movlw	.60				; reload count for next second
		movwf	seccnt
		bcf		serflg2,strtup	; make sure startup cycle flag is cleared
		btfss	serflg2,clkset	; skip if clock initialization has been completed
		goto	clkreq			; issue clock initialization request

;		Set up another transmit if output request is pending

		btfsc	serflg,serreq	; skip if no output request is pending
		btfsc	serflg,rcving	; skip if not currently receiving a serial message
		goto	adjclk			; delay request
		btfss	serflg,rcvrdy	; skip & issue serial output request if ready
		goto	adjclk			; delay request if so
clkreq	bsf		serflg,serxmt	; set transmit pending flag

;		Using line frequency as a reference, adjust clock to 8.16MHz on rising edge

adjclk	rlf		periodL,F		; double frequency resolution
		rlf		periodH,F
		btfsc	periodL,7		; round up MSB if LSB > 127
		incf	periodH,F		; MSB = 230 (60Hz) at 8.16MHz (with 247 Preset)
		movfw	periodH			; get period MSB
		addlw	.128			; adjust to center range for both 60Hz and 50Hz
		clrf	periodL			; clear period measurement
		clrf	periodH
		if		ac50hz == 0		; if not for 50Hz AC powerline
		  addlw	.154			; calculate error at 60Hz: W = 0 at 8.16MHz
		else					; must be for 50Hz
		  addlw	.101			; calculate error at 50Hz: W = 0 at 8.16MHz
		endif
		btfsc	STATUS,Z		; skip if clock needs adjusting
		goto	ledfls			; check LED flashing if not
		addlw	.128			; add 128 to set C for negative error (freq too low)
		btfsc	STATUS,C		; skip if error is positive
		goto	adjpos			; increase frequency if negative error

adjneg	decf	frqerr,F		; decrease frequency error count
		btfss	frqerr,6		; adjust frequency if max count
		goto 	ledfls			; check LED flashing if not
		movfw	nxttune			; recall oscillator tune setting
		btfss	STATUS,Z		; no adjust if already at minimum frequency (0)
		decf	nxttune,F		; decrease frequency (write at next T1 interrupt)
		goto	rsterr			; preset error word to mid range

adjpos	incf	frqerr,F		; increase frequency error count
		btfss	frqerr,6		; adjust frequency if max count
		goto	ledfls			; check LED flashing if not
		incf	nxttune,F		; increase frequency (write at next T1 interrupt)
		movfw	nxttune			; recall oscillator tune setting
		addlw	.192			; check if beyond max (64 + 192 = 256)
		movlw	.63				; for maximum = 63
		btfsc	STATUS,C		; skip if within range (0 - 63)
		movwf	nxttune			; set to maximum if beyond maximum
rsterr	movlw	b'00100000'		; preset frequency error to mid-range
		movwf	frqerr

;		Control LED flashing to indicate an error condition

ledfls	movf	ledcnt,F		; check if LED is being flashed
		btfsc	STATUS,Z		; skip if so
		goto	intrtn			; return with interrupts enabled if not
		movlw	b'00011111'		; to prevent any flash rate longer than 1 sec
		andwf	ledtim,F		; check if countdown timer is still zero
		btfsc	STATUS,Z		; skip if not
		goto	ledflp			; start timer and set LED status	
		decfsz	ledtim,F		; tick off one count & skip if done
		goto	intrtn			; return with interrupts enabled if not

		movlw	b'10001000'		; for LED continuous flash mode
		btfsc	ledcnt,7		; skip if LED is not in continuous flash mode
		iorwf	ledcnt,F		; keep in continuous flash mode
		decfsz	ledcnt,F		; tick off one flash cycle & skip if done
		goto	ledflp			; change LED status if not
		goto	ledoff			; turn of 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
		bcf		PORTC,LEDRED	; turn on red LED
		bsf		PORTA,LEDGRN
		btfss	ledcnt,0		; skip if flash count LSB is one
ledoff	bsf		PORTC,LEDRED	; turn off red LED
		bcf		PORTA,LEDGRN
		goto	intrtn			; return with interrupts

;		Turn off transmitter for single-phase output 1mS after zero crossing

timint	bcf		PIR1,TMR1IF		; clear Timer 1 interrupt flag
		call	xmtoff			; turn off transmitter

;		Update oscillator tune 1mS after zero crossing

oscadj	movfw	nxttune			; recall next oscillator tune setting
		xorlw	b'00100000'		; convert to offset binary for OSCTUNE
		banksel	OSCTUNE			; select Bank1
		movwf	OSCTUNE			; set next frequency
		clrf	BSR				; back to Bank0

;	call tstpls

		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	retfie					; return with interrupts enabled

;		Set LED flash count if not already flashing (number of flashes in W)

ledset	movf	ledcnt,F		; check if LED is already being flashed
		btfsc	STATUS,Z		; skip if so (leave last count)
		movwf	ledcnt			; set LED flash count
		return


		; INITIALIZATION SUBROUTINE
		;
		; Note: Clock Oscillator starts at 16 MHz internal by default

initiz	clrf	BSR				; select Bank 0
		clrf	PORTA			; initialize any port A I/O bits to zero
		movlw	b'00000100'		; all port C I/O bits are zero except RNGOUT
		clrf	PORTC			; initialize port C I/O bits
		movlw	.16				; set Timer 2 period for 120KHz PWM (8.16MHz/(4*17)
		movwf	PR2
		movlw	b'11001000'		; enable peripheral interrupts & interrupt on change
		movwf	INTCON
		clrf	T1GCON			; disable Timer 1 gate function
		clrf	T1CON			; disable Timer 1 with prescale=0 (time ref)
		clrf	T2CON			; disable Timer 2 with prescale=0 (120KHz PWM)
		clrf	TMR0			; clear Timer 0
		clrf	TMR1H			; clear Timer 1
		clrf	TMR1L
		clrf	TMR2			; clear Timer 2

		banksel	OSCCON			; select BANK1 for osc, interrupts, & A/D converter
		movlw	b'01110010'		; use internal osc at 8MHz
		movwf	OSCCON			; set oscillator control
		movlw	b'00010000'		; to start with frequency close to 8.16MHz
		movwf	OSCTUNE			; set oscillator tune setting
		movlw	b'00101010'		; ICSCLK, VPP, 60Hz inputs, LEDRED, C1OUT/T0CK, TP120K outputs
		movwf	TRISA			; define I/O port A directions
		movlw	b'00100010'		; RX, X10 are inputs, TX, PWM, RNGOUT, LEDGRN are outputs

;	movlw	b'00100011'		; leave LEDGRN as an input to monitor D/A out

		movwf	TRISC			; define I/O port C directions
		movlw	b'00111111'		; enable weak pull-ups, T0 Clk is C1Out\, no T0 prescale
		movwf	OPTION_REG		; set option register
		movlw	b'00000001'		; enable just Timer 1 overflow interrupt
		movwf	PIE1
		clrf	PIE2			; no other interrupts enabled
		clrf	ADCON0			; disable A/D converter
		clrf	ADCON1			; 
		movlw	b'00010111'		; set WDT for 2 sec timeout (31KHz/65536)
		movwf	WDTCON

		banksel	APFCON			; select BANK2 for comparator and D/A
		clrf	APFCON			; set all standard pin functions
		clrf	SRCON0			; make sure S-R latch is disabled
		clrf	SRCON1
		clrf	FVRCON			; disable fixed voltage reference
		movlw	b'10000000'		; enable D/A converter and not the output test point

;	movlw	b'10100000'		; enable D/A converter and output test point

		movwf	DACCON0
		movlw	.6				; set D/A converter to .94V  (6/32) x 5V
		movwf	DACCON1

		movlw	b'10100100'		; enable comparator 1 to monitor X10 BPF input
		movwf	CM1CON0
		movlw	b'00010001'		; no interrupts, DAC reference, use C12IN1- input 
		movwf	CM1CON1
		clrf	CM2CON0			; make sure comparator 2 is disabled
		clrf	CM2CON1
		movlw	b'10000000'		; to enable brown-out protection
		movwf	BORCON			; set brown-out control

		banksel	ANSELA			; select BANK3
		clrf	ANSELA			; all port A pins are for digital I/O
		movlw	b'00000010'		; select X10 bandpass filter input is analog
		movwf	ANSELC			; X10 BPF input is analog, all others are digital I/O
		movlw	.105			; set baud rate for 4811 -> 8.16MHz / 16*(N+1)
		movwf	SPBRGL			; N low byte
		clrf	BAUDCON			; no options, only need 8-bit baud rate generator
		movlw	b'00100100'		; enable asynchronous serial port transmitter (high speed)
		movwf	TXSTA
		movlw	b'10010000'		; enable asynchronous serial port receiver
		movwf	RCSTA

		banksel	WPUA			; select BANK4 for weak pull-up control
		movlw	b'00000010'		; to enable weak pull-up on ICSPCLK (serial timeout disable)
		movwf	WPUA			; enable port A weak pull-up on ICSPCLK
		movlw	b'00000100'		; to enable weak pull-up on RC2
		movwf	WPUC			; enable port C weak pull-up on Ready In

		banksel	CCPR1L			; select BANK5 for PWM control
		movlw	.6				; for 3.12uS pulsewidth
		movwf	CCPR1L			; set PWM duty cycle
		clrf	CCP1CON			; keep PWM disabled for now
		clrf	PWM1CON			; no PWM delay between phases
		clrf	CCP1AS			; no PWM auto-shutdown
		clrf	PSTR1CON		; keep PWM output disabled for now

		banksel	IOCAF			; select BANK7 for interrupt on change
		movlw	b'00100000'		; to enable interrupt on change for ZCD
		movwf	IOCAP			; set interrupt enable for ZCD positive edge
		movwf	IOCAN			; set interrupt enable for ZCD negative edge
		clrf	IOCAF			; clear any existing IOC flag bits
		movlw	b'11010110'		; to set reference clock output to 127.5KHz
		movwf	CLKRCON			; enable clock reference output

		clrf	BSR				; back to BANK0
		movlw	rambgn			; get pointer to beginning of RAM workspace
		movwf	FSR0L			; set indirect pointer to RAM workspace
		clrf	FSR0H
ramclr	clrf	INDF0			; clear RAM
		incf	FSR0L,F			; next address
		movlw	ramend			; get pointer to end of RAM
		subwf	FSR0L,W			; to end of RAM?
		btfss	STATUS,Z		; skip if RAM is all cleared
		goto	ramclr			; loop

		movlw	b'00100000'		; preset frequency error to mid-range
		movwf	frqerr
		movlw	.48				; start with AGC level at .94V (5V full-scale)
		movwf	agclvl
		movlw	b'00110000'		; OSCTUNE preset close to 8.16MHz
		movwf	nxttune			; also save at RAM workspace
		movlw	.60				; allow 1 second for ring indicator startup cycle
		movwf	seccnt			; set second count
		clrf	stsflg			; clear status flags
		clrf	serflg			; clear serial port flags
		clrf	serflg2			; clear additional serial port flags
		bsf		serflg2,strtup	; set flag for ring indicator start up cycle
		bsf		T2CON,TMR2ON	; start PWM timer
		bsf		serflg2,rngena	; set serial ring indicator enabled by default
		retfie					; return from initialize & enable interrupts


;		SUBROUTINE TO PROCESS RECEIVED DATA
;
;		This routine is called after the end of the X10 reception window.
;		A count of 48 or more in cyccnt normally indicates a logic "1".

;		Process X10 48-cycle fixed decode

rcvinp	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		stsflg,colisn	; ensure collision is cleared
		bcf		stsflg,rcverr	; ensure receive error is cleared
		bcf		stsflg,xtdcmd	; ensure extended command flag is cleared

;		Finish up sequential bright/dim command processing

		btfss	stsflg,rcvpnd	; skip & process pending received data
		goto	savzer			; save zero in the receive shift register
		btfss	stsflg,dimcmd	; skip if processing a bright/dim command
		goto	rcvxfr			; transfer unpacked message to transmit buffer
		btfsc	stsflg,xmtmsg	; skip if not transmitting a message
		goto	rcvxfr			; transfer unpacked message to transmit buffer
		bsf		dcdbuf,1		; set bright/dim bit in buffer header
		movfw	dimlvl			; get bright/dim level
		movwf	dcdbuf+2		; save in decode buffer
rcvxfr	call	xfrbuf			; transfer unpacked message to transmit buffer

		bcf		stsflg,rcvpnd	; clear received data pending flag
		bsf		serflg,xmtful	; set transmit buffer full flag
		movlw	.60				; reload count for next second
		movwf	seccnt
dimskp	clrf	dimlvl			; clear dim level
		bcf		stsflg,dimcmd	; clear dim command flag

; 		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	incf	onecnt,F		; increment number of "1" bits
		clrf	gapcnt			; clear gap count for a "1"
		btfss	stsflg,xmtmsg	; skip if a message is being transmitted
		goto	bitled			; flash LED for a received "1"
		btfsc	stsflg,outbit	; skip if transmitter was off this half cycle
		goto	bitled			; flash LED for a transmitted "1"

;		No collision if in gap after transmission has completed

		movf	dimcnt,F		; check if doing a dim sequence
		btfss	STATUS,Z		; skip if not
		goto	abtxmt			; abort if collision during dim sequence
		movfw	xmtcnt			; get transmit count
		addlw	.250			; check if collision is in trailing gap
		btfss	STATUS,C		; abort transmission if not
		goto	bitled			; no abort for collision in trailing gap

;		Abort transmission & schedule re-transmit for a collision

abtxmt	clrf	xmtcnt			; no more bits to transmit
		movlw	.2				; for 1 re-transmit attempts maximum
		movf	xmtagn,F		; check re-transmit count
		btfsc	STATUS,Z		; skip if count is not zero
		movwf	xmtagn			; set maximum re-transmit limit
		bsf		stsflg,colisn	; indicate a collision was detected
		movlw	ledcol			; number of flashes to indicate a collision
		call	ledset			; set LED count for number of flashes

bitled	movf	ledcnt,F		; check if LED is being flashed
		btfsc	STATUS,Z		; skip if so
		bcf		PORTC,LEDRED	; turn on green LED for a received "1"
		bsf		STATUS,C		; restore bit status to "1" for shift
bitsav	rlf		rcvsrg+7,F		; shift bit into receive shift register
		rlf		rcvsrg+6,F
		rlf		rcvsrg+5,F
		rlf		rcvsrg+4,F
		rlf		rcvsrg+3,F
		rlf		rcvsrg+2,F
		rlf		rcvsrg+1,F
		rlf		rcvsrg,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	rcvsrg+7		; 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 	clrmsg			; 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	btfsc	stsflg,xmtmsg	; process received command if not transmitting
		goto	clrmsg			; just dump message if transmitting
		btfss	rcvsrg+7,0		; skip if received bit was a "1"
		decf	onezer,F		; decrement 1/0 ratio if it was a "0"
		btfsc	rcvsrg+7,1		; skip if received bit was a "0"
		incf	onezer,F		; increment 1/0 ratio if it was a "1"

;		Check if all data bits have been received

		incf	rcvcnt,F		; increment received bit count
		movfw	rcvcnt			; get received bit count
		btfss	stsflg,xtdcmd	; skip if an extended command
		addlw	.40				; adjust to check for 22-bit standard message
		addlw	.193			; extended: 62+193=255; normal 22+40+193=255
		btfsc	STATUS,C		; skip and check for errors until received 22 or 62 bits
 		goto	rcvout			; no error check after receiving all of first half

;		Check for an error in first 22 or 62 bits (should always be 1-3 after start)

		btfsc	onezer,7		; skip if 1/0 ratio is positive
		goto	errset			; set error flag if negative overflow
		movfw	onezer			; get 1/0 ratio
		btfsc	STATUS,Z		; skip if > 0 (0 = not enough "1"s)
		goto	errset			; set error flag if not enough "1"s
		addlw	.252			; 4 + 252 -> carry set (too many "1"s)
		btfss	STATUS,C		; skip & set error if too many "1"s
		goto	rcv22b			; check if received exactly 22 bits

errset	bsf		stsflg,rcverr	; set error flag
		call	incagc			; increase AGC level by 1 DAC bit
		goto	rcvclr			; clear receive counts

;		Check if exactly 22 bits have been received

rcv22b	movfw	rcvcnt			; get received bit count
		addlw	.234			; check for receiving exactly 22 bits
		btfss	STATUS,C		; skip if received at least 22 bits
		return					; just return if not
		btfss	STATUS,Z		; skip & process if it has received exactly 22 bits
		goto	rcvout			; just output if received more than 22 bits

;		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

;		Check for extended message

		movfw	rcvsrg+7		; get 8 LSBs of X10 function word
		call	unpak4			; unpack function W=0000FFFF
		btfsc	rcvsrg+6,1		; skip if MSB of X10 function word is zero
		addlw	b'00010000'		; set MSB of decoded function word to "1"
		movwf	funcod			; save function code

;		Check for extended message codes (01111 or 11001)

		movlw	b'00001111'		; to check for extended code
		xorwf	funcod,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	funcod,W		; was it the extended data code?
		btfss	STATUS,Z		; skip if extended data code
		goto	set22b			; set standard 22-bit message if not extended
rcvxtm	bsf		stsflg,xtdcmd	; set flag to indicate receiving an extended command
		movlw	.62				; 31-bit extended message (for end of receive)
		movwf	msglen			; set message length
		goto	rcvout			; no repeat

set22b	movlw	.22				; for 1/2 of standard 22-bit doublet
		movwf	msglen			; set message length

;		Check for bright or dim commands (01011 or 01001)

		movlw	b'00001001'		; to check for "DIM" command
		xorwf	funcod,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	funcod,W		; is it "BRIGHT"?
		btfss	STATUS,Z		; skip if "BRIGHT"
		goto	dimclr			; clear dim level if not a bright/dim command

dimset	movlw	.3				; to increment dim level 3 counts for micro-dim
		btfss	stsflg,dimcmd	; skip & use full dim step if receiving a dim sequence
		goto	dimadd			; increment dim level
		movlw	.11				; to increase dim level by 11 (11/210 = 5.2%)
dimadd	addwf	dimlvl,F		; increase dim level
		movlw	.46				; to check maximum dim level (46 + 210 = 256)
		addwf	dimlvl,W		; check if above maximum
		movlw	.210			; maximum dim level is 210
		btfsc	STATUS,C		; skip if not above maximum
		movwf	dimlvl			; set dim level to maximum
		bsf		stsflg,dimcmd	; make sure dim flag is set for a bright/dim command
		movlw	.22				; message length for sequential dims is just 22 bits each
		movwf	msglen			; set short message length
		goto	rcvout			; continue processing the received command

dimclr	clrf	dimlvl			; clear dim level if not a bright/dim command
rcvout	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	rcvsrg+7		; recall most recent 8 bits
		andlw	b'00111111'		; mask all but last 6 bits
		btfss	STATUS,Z		; is it a gap (6 zeros in a row)?
		goto	chknxt			; check for next "start" if not
		movfw	rcvcnt			; get received bit count
		addlw	.16				; 16 bits left in message
		movwf	msglen			; set message length
		return

chknxt	andlw	b'00001111'		; mask all but last 4 bits
		xorlw	b'00001110'		; check for next "start" data pattern (1110)?
		btfss	STATUS,Z		; skip if "start"
		return					; done with bit processing if not

		movfw	rcvcnt			; get received bit count
		addlw	.17				; 18 bits left in message (allow for trailing 0)
		movwf	msglen			; set message length
		return

msgbad	bsf		stsflg,rcverr	; indicate a receive error for a bad message
		goto	clrmsg			; clear message

chkend	movfw	msglen			; recall message length
		subwf	rcvcnt,W		; is message complete (C=1 if so)?
		btfss	STATUS,C		; skip if message is complete
		return					; just return if not

		call	unpack			; unpack message and move into X10 decode buffer
		bsf		stsflg,rcvpnd	; indicate received data is pending transfer
		btfsc	serflg2,rngena	; skip if serial ring indicator is not enabled
		bcf		PORTC,RNGOUT	; make sure serial ring out is asserted (high)

clrmsg	btfsc	stsflg,xmtmsg	; skip & process receive error if not transmitting
		goto	clrerr			; no receive error while transmitting
		movlw	lederr			; number of flashes to indicate a receive error
		btfsc	stsflg,rcverr	; skip if error flag is not set
		call	ledset			; set LED count for number of flashes
clrerr	bcf		stsflg,rcverr	; clear error flag
rcvclr	clrf	rcvcnt			; clear counters
		clrf	onecnt
		clrf	onezer			; clear 1/0 ratio
		return					; done with bit processing


;		SUBROUTINE TO UNPACK RECEIVED DATA
;
;		Moves data from X10 receive register to X10 decode buffer
;
;		Receive register data is as follows: (rcvsrg0 ... rcvsrg7)
;		22-bit command:  xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xx1110HH HHHHHHFF FFFFFFFF
;		62-bit extended: xx1110HH HHHHHHFF FFFFFFFF UUUUUUUU DDDDDDDD DDDDDDDD CCCCCCCC CCCCCCCC
;
;		X10 Decode Buffer data is 2 to 5 bytes:  (dcdbuf0 ... dcdbuf4)
;			normal address:	00000000 + (Housecode/Unitcode)
;			normal command: 00000001 + (Housecode/Function)
;			bright/dim cmd:	00000011 + (Housecode/Function) + (% Dim)
;			extended cmd:	00000111 + (Housecode/Function) + (Unitcode) + (Data) + (Command)

unpack	clrf	BSR				; make sure BANK0 is selected
		clrf	dcdbuf			; start with decode buffer cleared
		clrf	dcdbuf+1
		clrf	dcdbuf+2
		clrf	dcdbuf+3
		clrf	dcdbuf+4
		movlw	.24				; delay 0.2 second for another command to start before upload
		movwf	reqdly			; set output request delay
		btfsc	stsflg,xtdcmd	; skip if not an extended command
		goto	unpkxt			; unpack an extended command

;		Unpack standard command

		rrf		rcvsrg+5,F		; shift house & address/function codes into byte alignment
		rrf		rcvsrg+6,F
		rrf		rcvsrg+7,F
		rrf		rcvsrg+5,F
		rrf		rcvsrg+6,F
		rrf		rcvsrg+7,F		; shift address/function bit into C
		btfsc	STATUS,C		; skip if an address
		bsf		dcdbuf,0		; set function bit in header
		movfw	rcvsrg+6		; get X10 housecode byte
		call	unpak4			; unpack housecode W=0000HHHH
		movwf	dcdbuf+1		; store housecode
		swapf	dcdbuf+1,F		; move housecode to 4 MSBs
		movfw	rcvsrg+7		; get X10 address/function byte
		call	unpak4			; unpack address/function W=0000FFFF
		addwf	dcdbuf+1,F		; add address/function to housecode 2nd transmit buffer byte
		clrf	rcvsrg+7		; purge receive buffer to avoid start pattern error
		return

;		Unpack extended command

unpkxt	movlw	b'00000111'		; load header for extended command
		movwf	dcdbuf			; store extended header into decode buffer
		rrf		rcvsrg,F		; shift house & address/function codes into byte alignment
		rrf		rcvsrg+1,F
		rrf		rcvsrg+2,F
		rrf		rcvsrg,F
		rrf		rcvsrg+1,F
		rrf		rcvsrg+2,F		; (address/function bit is not needed)

;		Unpack standard portion of extended command

		movfw	rcvsrg+1		; get X10 housecode byte
		call	unpak4			; unpack housecode W=0000HHHH
		movwf	dcdbuf+1		; store housecode
		swapf	dcdbuf+1,F		; move housecode to 4 MSBs
		movfw	rcvsrg+2		; get X10 address/function byte
		call	unpak4			; unpack address/function W=0000FFFF
		addwf	dcdbuf+1,F		; add address/function to housecode 2nd transmit buffer byte

;		Unpack extended portion of extended command

		movfw	rcvsrg+3		; get X10 unitcode byte
		call	unpak4			; unpack unitcode W=0000UUUU
		movwf	dcdbuf+2		; store unitcode in 3rd transmit buffer byte
		movfw	rcvsrg+4		; get first X10 data byte
		call	unpak4			; unpack first X10 data byte W=0000DDDD
		movwf	dcdbuf+3		; store first data byte
		swapf	dcdbuf+3,F		; move first data byte to 4 MSBs
		movfw	rcvsrg+5		; get second X10 data byte
		call	unpak4			; unpack second X10 data byte W=0000DDDD
		addwf	dcdbuf+3,F		; add second data byte to 4th transmit buffer byte
		movfw	rcvsrg+6		; get first X10 command byte
		call	unpak4			; unpack first X10 command byte W=0000CCCC
		movwf	dcdbuf+4		; store first command byte
		swapf	dcdbuf+4,F		; move first command byte to 4 MSBs
		movfw	rcvsrg+7		; get second X10 command byte
		call	unpak4			; unpack second X10 command byte W=0000CCCC
		addwf	dcdbuf+4,F		; add second command byte to 5th transmit buffer byte
		clrf	rcvsrg+7		; purge receive buffer to avoid start pattern error
		return

;		Unpack X10 byte to 4-bit nibble

;		Enter with W = X10 byte to decode: 12345678
;		Returns with decoded data in W LSBs: 00001357

unpak4	movwf	temp0			; save byte to decode
		clrf	temp1			; clear temp register
		rlf		temp0,F			; shift out 1st bit
		rlf		temp1,F			; shift 1st bit into result
		rlf		temp0,F			; shift out 1st compliment and 2nd bit
		rlf		temp0,F
		rlf		temp1,F			; shift 2nd bit into result
		rlf		temp0,F			; shift out 2nd compliment and 3rd bit
		rlf		temp0,F
		rlf		temp1,F			; shift 3rd bit into result
		rlf		temp0,F			; shift out 3rd compliment and 4th bit
		rlf		temp0,F
		rlf		temp1,F			; shift 4th bit into result
		movfw	temp1			; W = result
		return


;		TRANSFER UNPACKED COMMAND TO SERIAL TRANSMIT BUFFER

;		This routine transfers data into the 10 byte serial transmit buffer.
;		Data is lost if there is not room to transfer the complete command.
;
;		X10 Decode Buffer data is 2 to 5 bytes:  (dcdbuf0 ... dcdbuf4)
;			normal address:	00000000 + (Housecode/Unitcode)
;			normal command: 00000001 + (Housecode/Function)
;			bright/dim cmd:	00000011 + (Housecode/Function) + (% Dim)
;			extended cmd:	00000111 + (Housecode/Function) + (Unitcode) + (Data) + (Command)

xfrbuf	movfw	xmtbuf			; get number of bytes currently in transmit buffer
		btfss	STATUS,Z		; skip if zero (buffer cleared)
		goto	xfrbu1			; check if enough room for the command
		movlw	.2				; start with 2 bytes in buffer - size and mask
		movwf	xmtbuf			; initialize size
		clrf	xmtbuf+1		; clear address/function mask
		goto	xfrbu3			; begin transfer

xfrbu1	lsrf	dcdbuf,W		; shift extended and bright/dim bits to 2 LSBs
		andlw	b'00000011'		; make sure only 0-3
		addwf	xmtbuf,W		; add number of bytes currently in transmit buffer
		addlw	.246			; check if there is enough room left (9+246=255)
		btfss	STATUS,C		; skip if not enough room
		goto	xfrbu3			; begin transfer
		return					; just return if not enough room

xfrbu3	decf	xmtbuf,W		; get the number of bytes in xmtbuf (-1)
		movwf	temp0			; save for shift count
		clrf	temp1			; clear shift temp
		rrf		dcdbuf,W		; shift address/function bit into C
xfrbu4	rlf		temp1,F			; shift into temp
		decfsz	temp0,F			; skip if bit is in correct position
		goto	xfrbu4			; loop until address/function bit is at proper position
		movfw	temp1			; get shifted address/function bit
		iorwf	xmtbuf+1,F		; set appropriate address/function bit in xmtbuf

		movfw	xmtbuf			; get current transmit buffer size count
		addlw	xmtbuf			; add physical memory offset of transmit buffer
		movwf	FSR0L			; set indirect pointer to transmit buffer
		clrf	FSR0H			; clear indirect pointer MSB

;		transfer first byte for normal commands

		movfw	dcdbuf+1		; get first data byte in decode buffer
		movwf	INDF0			; move it to transmit buffer
		incf	xmtbuf,F		; increment size count
		btfss	dcdbuf,1		; skip if bright/dim or extended commands
		return					; done

;		transfer second byte for bright/dim or extended commands

		incf	FSR0L,F			; increment indirect pointer
		movfw	dcdbuf+2		; get second data byte in decode buffer
		movwf	INDF0			; move it to transmit buffer
		incf	xmtbuf,F		; increment size count
		btfss	dcdbuf,2		; skip if extended command
		return					; done

;		transfer third & fourth bytes for extended commands

		incf	FSR0L,F			; increment indirect pointer
		movfw	dcdbuf+3		; get third data byte in decode buffer
		movwf	INDF0			; move it to transmit buffer
		incf	xmtbuf,F		; increment size count
		incf	FSR0L,F			; increment indirect pointer
		movfw	dcdbuf+4		; get fourth data byte in decode buffer
		movwf	INDF0			; move it to transmit buffer
		incf	xmtbuf,F		; increment size count
		return


;		SUBROUTINE TO PROCESS TRANSMIT DATA
;
;		This routine is called to set up the next bit to be transmitted

xmtbit	clrf	BSR				; make sure BANK0 is selected
		bcf		stsflg,outbit	; clear previous output bit
		bcf		STATUS,C		; make sure C is clear to shift in zeros for gap
		rlf		xmtsrg+7,F		; shift next data bit into C
		rlf		xmtsrg+6,F
		rlf		xmtsrg+5,F
		rlf		xmtsrg+4,F
		rlf		xmtsrg+3,F
		rlf		xmtsrg+2,F
		rlf		xmtsrg+1,F
		rlf		xmtsrg+0,F
		btfsc	STATUS,C		; skip if next bit to transmit is a zero
		bsf		stsflg,outbit	; set output bit to a logic "1"
		movlw	.187			; to check for 2nd half if extended doublet (68+188=256)
		addwf	xmtcnt,W		; check if ready to load 2nd doublet of extended command
		btfsc	STATUS,Z		; skip if not
		call	pakagn			; pack 2nd half of extended command
		decfsz	xmtcnt,F		; decrement transmit bit count & skip if done
		return

		movf	dimcnt,F		; check transmitting a dim/bright sequence
		btfss	STATUS,Z		; skip if not
		return					; delay issuing ready until sequence is done

		bsf		serflg,rcvrdy	; set serial receive ready flag
		bsf		serflg,srdy55	; set flag to send "55" serial port ready
		bsf		serflg,serxmt	; set flag to transmit "55" ready
		clrf	xmtagn			; make sure re-transmit count is also cleared
		return


;		SUBROUTINE TO ASSEMBLE SHIFT REGISTER DATA FOR TRANSMIT
;
;		Encodes house code and address/function code into transmit message
;			data is encoded from serial receive buffer (rcvbuf)
;			data is double-buffered for possible re-transmit in case of a collision
;		Transmit register data is as follows: (xmtsrg0 ... xmtsrg7)
;		44-bit command:  1110HHHH HHHHFFFF FFFFFF11 10HHHHHH HHFFFFFF FFFF0000 00000000 00000000
;		62-bit extended: 1110HHHH HHHHFFFF FFFFFFUU UUUUUUDD DDDDDDDD DDDDDDCC CCCCCCCC CCCCCC00

packit	clrf	BSR				; make sure BANK0 is selected
		movfw	rcvbuf			; move 5 bytes from receive register into double buffer
		movwf	dblbuf
		movfw	rcvbuf+1
		movwf	dblbuf+1
		movfw	rcvbuf+2
		movwf	dblbuf+2
		movfw	rcvbuf+3
		movwf	dblbuf+3
		movfw	rcvbuf+4
		movwf	dblbuf+4

pakagn	movlw	b'00001110'		; set up 4-bit start pattern
		movwf	xmtsrg			; save in first byte of transmit shift register
		swapf	dblbuf+1,W		; get 4-bit housecode (4 MSBs -> W LSBs)
		andlw	b'00001111'		; mask all but 4 LSBs
		call	expx10			; convert housecode to expanded X10 code
		movwf	xmtsrg+1		; save in 2nd byte of transmit shift register
		movfw	dblbuf+1		; get 4-bit address/function code
		andlw	b'00001111'		; mask all but 4 LSBs
		call	expx10			; convert address/function code to expanded X10 code
		movwf	xmtsrg+2		; save in 3rd byte of transmit shift register
		rlf		xmtsrg+2,F		; shift left 4 bits so start pattern is at xmtsrg MSB
		rlf		xmtsrg+1,F
		rlf		xmtsrg,F
		rlf		xmtsrg+2,F
		rlf		xmtsrg+1,F
		rlf		xmtsrg,F
		rlf		xmtsrg+2,F
		rlf		xmtsrg+1,F
		rlf		xmtsrg,F
		rlf		xmtsrg+2,F		; xmtsrg 3rd byte contains FFFFxxxx
		rlf		xmtsrg+1,F		; xmtsrg 2nd byte contains HHHHFFFF
		rlf		xmtsrg,F		; xmtsrg 1st byte contains 1110HHHH
		movfw	xmtsrg+2		; get 3rd byte
		andlw	b'11110000'		; mask undefined bits
		movwf	xmtsrg+2		; save it
		btfsc	dblbuf,1		; skip if function/address bit is 0
		bsf		xmtsrg+2,3		; set function/address X10 bit to 1
		btfss	dblbuf,1		; skip if function/address bit is 1
		bsf		xmtsrg+2,2		; set function/address X10 compliment bit to 1
		btfsc	dblbuf,0		; skip if not an extended message
		goto	packxt			; continue packing an extended message

;		Only transmit first half for dim or bright command with specific count

		btfss	dblbuf,1		; skip and check for bright/dim if a command
		goto	pak2nd			; continue with second half of a standard message
		movfw	dblbuf+1		; get 4-bit address/function code
		andlw	b'00001110'		; mask all but 3 bits that determine dim/bright
		xorlw	b'00000100'		; check for dim/bright command
		btfss	STATUS,Z		; skip if dim or bright command
		goto	pak2nd			; pack second half of standard command if not

		movfw	dimcnt			; check if already transmitting a dim/bright sequence
		btfss	STATUS,Z		; skip if not
		goto	dimagn			; count off another dim/bright cycle
		movfw	dblbuf			; get header byte
		andlw	b'11111000'		; mask all but dim/bright repeat count
		btfsc	STATUS,Z		; skip for special handling when count is specified
		goto	pak2nd			; transmit normal dim/bright doublet if no count
		movwf	dimcnt			; save at bright/dim count
		bcf		STATUS,C		; clear carry bit
		rrf		dimcnt,F		; shift count to 5 LSBs
		rrf		dimcnt,F
		rrf		dimcnt,F
		movfw	dimcnt			; get dim count
		addlw	.234			; check for maximum (22+234 = 256)
		btfss	STATUS,C		; skip if > 22
		goto	dimagn			; complete setup for first dim command
		movlw	.22				; 22 maximum dim/bright commands
		movwf	dimcnt			; set dim count to maximum

dimagn	movlw	.22				; 22 bits to transmit, with no trailing gap
		movwf	xmtcnt			; set transmit bit count
		decfsz	dimcnt,F		; count off 1 dim/bright transmission & skip if done
		return

		movlw	.28				; 28 bits for last dim command, including trailing gap
		movwf	xmtcnt			; set transmit bit count for end of sequence
		clrf	xmtsrg+3		; make sure gap bits are cleared
		goto	pakdon			; finished setting up bright/dim transmission

;		Continue assembling second half of a standard message

pak2nd	bsf		xmtsrg+2,1		; set first two bits of the second start pattern to "11" 
		bsf		xmtsrg+2,0
		movfw	xmtsrg			; copy command first half to second half
		movwf	xmtsrg+3
		movfw	xmtsrg+1
		movwf	xmtsrg+4
		movfw	xmtsrg+2
		movwf	xmtsrg+5
		rlf		xmtsrg+5,F		; shift left 2 bits to leave only "10" of start pattern
		rlf		xmtsrg+4,F
		rlf		xmtsrg+3,F
		rlf		xmtsrg+5,F
		rlf		xmtsrg+4,F
		rlf		xmtsrg+3,F
		movlw	b'11110000'		; bit mask to clear trailing 4 bits
		andwf	xmtsrg+5,F		; clear trailing 4 bits of 6th byte
		clrf	xmtsrg+6		; clear shift register 7th & 8th bytes
		clrf	xmtsrg+7
		movlw	.50				; 50 bits to transmit, including trailing gap
		movwf	xmtcnt			; set transmit count
		goto	pakdon			; finished setting up normal transmission

;		Assemble extended portion of an extended message

packxt	movfw	dblbuf+2		; get unit code in 4 LSBs
		andlw	b'00001111'		; mask all but 4 LSBs	
		call	expx10			; convert unit code to expanded X10 code
		movwf	xmtsrg+3		; save in 4th byte of transmit shift register
		swapf	dblbuf+3,W		; move 4 MSBs of data byte into W LSBs
		andlw	b'00001111'		; mask all but 4 LSBs
		call	expx10			; convert to expanded X10 code
		movwf	xmtsrg+4		; save in 5th byte of transmit shift register
		movfw	dblbuf+3		; get data byte again
		andlw	b'00001111'		; mask all but 4 LSBs
		call	expx10			; convert code to expanded X10 code
		movwf	xmtsrg+5		; save in 6th byte of transmit shift register
		swapf	dblbuf+4,W		; move 4 MSBs of command byte into W LSBs
		andlw	b'00001111'		; mask all but 4 LSBs
		call	expx10			; convert to expanded X10 code
		movwf	xmtsrg+6		; save in 7th byte of transmit shift register
		movfw	dblbuf+4		; get command byte again
		andlw	b'00001111'		; mask all but 4 LSBs
		call	expx10			; convert code to expanded X10 code
		movwf	xmtsrg+7		; save in 8th byte of transmit shift register

;		Splice extended portion onto first half of standard command

		rrf		xmtsrg+2,F		; shift 3rd byte right 2 bits
		rrf		xmtsrg+2,F
		rlf		xmtsrg+7,F		; shift bytes 3-8 left 2 bits to complete packing
		rlf		xmtsrg+6,F
		rlf		xmtsrg+5,F
		rlf		xmtsrg+4,F
		rlf		xmtsrg+3,F
		rlf		xmtsrg+2,F
		rlf		xmtsrg+7,F
		rlf		xmtsrg+6,F
		rlf		xmtsrg+5,F
		rlf		xmtsrg+4,F
		rlf		xmtsrg+3,F
		rlf		xmtsrg+2,F
		movlw	b'11111100'		; bit mask to clear trailing 2 bits
		andwf	xmtsrg+7,F		; clear trailing 2 bits of 8th byte
		movlw	.130			; 130 bits to transmit for doublet, including trailing gap
		movf	xmtcnt,F		; check if transmit count already set for extended command
		btfsc	STATUS,Z		; skip if so
		movwf	xmtcnt			; set transmit count

pakdon	movf	xmtagn,F		; check if doing a re-transmit
		btfss	STATUS,Z		; skip & get ready for next serial input if not
		return					; just return if a re-transmit

		clrf	rcvptr			; clear serial receive buffer pointer
		bcf		serflg,rcvful	; serial receive buffer is now empty
		return					; delay ready until transmit is complete


;		SERIAL DATA INPUT

;		This routine receives serial data from the RS232 port

serinp	clrf	BSR				; select BANK0
		btfsc	PIR1,RCIF		; skip if serial input buffer is empty
		goto	serrx1			; process received serial data
		btfsc	serflg,rcvful	; skip if not waiting to transmit
		goto	serx10			; begin transmit if X10 output buffer is empty
		return					; just return if not waiting to transmit

;		Process serial data

serrx1
		banksel	RCREG			; select BANK3 for serial port
		btfsc	RCSTA,OERR		; skip if no overflow error
		goto	serrst			; reset serial receiver, & flash LED if overflow
		btfsc	RCSTA,FERR		; skip if no framing error
		goto	sererr			; flush bad data & flash LED to indicate error
		movfw	RCREG			; read receive register
		clrf	BSR				; back to BANK0
		movwf	sertmp			; save receive data
		btfsc	serflg,rcving	; skip if not receiving serial data
		goto	sersav			; save received data in receive buffer

;		Check if new data header or a control byte

		btfss	sertmp,2		; skip if a new data header
		goto	serctl			; check for control byte
		btfsc	serflg,rcvrdy	; skip if not ready to receive serial data
		goto	serrcv			; start receiving a new message
		goto	sererr			; flush data and flash LED to indicate error

;		Check for checksum acknowledge or serial output acknowledge

serctl	btfsc	serflg,chkout	; skip if not waiting for checksum acknowledge
		goto	serr00			; check for checksum acknowledge
		btfsc	serflg,serreq	; skip if not waiting for serial output acknowledge
		goto	serrc3			; check for serial output acknowledge

;		Check for the CM11A control functions

		movfw	sertmp			; recall received serial data
		sublw	H'EB'			; check for CM11A serial ring enable
		btfsc	STATUS,Z		; skip if not
		goto	sreack			; acknowledge serial ring enable
		movfw	sertmp			; recall received serial data
		sublw	H'DB'			; check for CM11A serial ring disable
		btfsc	STATUS,Z		; skip if not
		goto	srdack			; acknowledge serial ring disable
		movfw	sertmp			; recall received serial data	
		sublw	H'FB'			; check for EEPROM download
		btfsc	STATUS,Z		; skip if not EEPROM download
		goto	serdld			; simulate CM11A EEPROM and macro download
		movfw	sertmp			; recall received serial data
		sublw	H'9B'			; check for CM11A clock initialization command
		btfsc	STATUS,Z		; skip & simulate CM11A response if so
		goto	serclk			; simulate CM11A clock initialization download 
		movfw	sertmp			; recall received serial data
		sublw	H'8B'			; check for CM11A status check
		btfss	STATUS,Z		; skip & simulate CM11A response if so
		return					; just ignore

;		Simulate the CM11A status response

		movlw	.14				; to output 14 bytes
		movwf	sercnt			; save to count number of bytes
		banksel	TXREG			; select BANK3 for serial port
sersts	btfss	TXSTA,TRMT		; skip if transmit register can accept another byte
		goto	sersts			; loop waiting for transmit register to empty
		movlw	H'00'			; to transmit all zeros
		movwf	TXREG			; output serial data
		decfsz	sercnt,F		; count off one byte
		goto	sersts			; loop for 14 bytes
		clrf	BSR				; back to BANK0
		bsf		serflg,rcvrdy	; make sure serial receive ready flag is set
		return

;		Simulate CM11A data download

serclk	bsf		serflg2,clkpnd	; indicate clock initialization is pending
		bsf		serflg2,clkset	; set clock initialization completed flag

serdld	clrf	chksum			; start with checksum cleared
		bsf		stsflg,dnload	; set flag indicating a download

serdnx	clrf	BSR				; make sure BANK0 is selected
		movlw	.200			; 200mS max delay for next character
		movwf	sercnt			; save at serial temp register
serdlp	btfsc	PIR1,RCIF		; skip if serial input buffer is empty
		goto	serrx2			; process received serial data
		movlw	.254			; for 996uS delay
		movwf	dlycnt
dly100	nop
		nop
		nop
		nop
		nop
		decfsz	dlycnt,F
		goto	dly100			; loop for 100uS delay
		decfsz	sercnt,F		; count off one loop
		goto	serdlp			; loop for max delay

		bcf		serflg,rcvrdy	; clear receive ready flag until next "55" sent
		bcf		serflg,rcving	; clear serial receive flag
		bsf		serflg,chkout	; set flag indicating checksum is being sent
		clrf	dimcnt			; make sure dim count is cleared
		movfw	chksum			; get checksum
		goto	sertxw			; transmit over serial port

serrx2
		banksel	RCREG			; select BANK3 for serial port
		btfsc	RCSTA,OERR		; skip if no overflow error
		goto	serrst			; reset serial receiver, & flash LED if overflow
		btfsc	RCSTA,FERR		; skip if no framing error
		goto	sererr			; flush bad data & flash LED to indicate error
		movfw	RCREG			; read receive register
		clrf	BSR				; back to BANK0
		addwf	chksum,F		; add to checksum
		clrwdt					; clear watchdog timer
		goto	serdnx			; download the next character

;		Serial ring enable acknowlege

sreack	bsf		serflg2,srepnd	; indicate serial ring enable is pending
		bcf		serflg2,srdpnd	; make sure serial ring disable pending is cleared
		goto	serrak			; send checksum acknowledge

;		serial ring disable acknowledge

srdack	bsf		serflg2,srdpnd	; indicate serial ring disable is pending
		bcf		serflg2,srepnd	; make sure serial ring enable pending is cleared

serrak	movfw	sertmp			; recall received serial data
		movwf	chksum			; save at checksum for echo
		bsf		serflg,chkout	; set flag indicating checksum is being sent
		bsf		serflg,serxmt	; set flag to transmit checksum
		bcf		serflg,rcvrdy	; clear receive ready flag until next "55" sent
		bcf		serflg,rcving	; clear serial receive flag
		return

;		Check for checksum acknowledge

serr00	btfsc	serflg,serxmt	; skip if checksum has been sent to transmitter
		return					; just discard the data

		clrf	BSR				; back to BANK0
		bcf		serflg,chkout	; clear checksum output flag
		btfsc	stsflg,dnload	; skip if not simulating a download
		goto	serrdy			; indicate ready to receive serial data
		movfw	sertmp			; recall received serial data
		btfsc	STATUS,Z		; skip if not checksum OK
		goto	chkack			; process checksum acknowledge
		goto	serrcv			; begin receiving new message

serrdy	bcf		stsflg,dnload	; clear download flag
		bsf		serflg,rcvrdy	; set serial receive ready flag
		bsf		serflg,srdy55	; set flag to send "55" serial port ready
		bsf		serflg,serxmt	; set flag to transmit "55" ready
		clrf	timout			; no timeout for next serial input
		return

;		Check for serial output acknowledge

serrc3	movfw	sertmp			; recall received serial data
		sublw	b'11000011'		; check for "C3" serial output acknowledge
		btfss	STATUS,Z		; skip if so
		goto	serrcv			; must be data, store in receive buffer
		bsf		PORTC,RNGOUT	; deassert serial ring out (low)
		bcf		serflg,serreq	; clear serial output request
		bsf		serflg,rcvrdy	; set serial receive ready flag
		bsf		serflg,serxmt	; set serial transmit flag to begin output
		return

;		Save serial input data in receive buffer

serrcv	clrf	rcvptr			; reset receive buffer pointer to 0
		clrf	chksum			; make sure checksum is cleared
		bcf		serflg,chkout	; make sure serial checksum flag is cleared
		bsf		serflg,rcvrdy	; make sure serial receive ready flag is set
		bsf		serflg,rcving	; set receiving serial data flag

sersav	btfss	serflg,rcvrdy	; save incoming data if ready to receive
		goto	sererr			; dump data & indicate error if XTB is not ready
		movlw	.60				; 1 second timeout
		movwf	timout			; set timeout window for next serial input
		movfw	sertmp			; recall received serial data
		addwf	chksum,F		; add to checksum
		movfw	rcvptr			; get receive buffer pointer (0 to 4)
		addlw	rcvbuf			; add physical memory offset of buffer
		movwf	FSR0L			; set indirect pointer to receive buffer
		clrf	FSR0H
		movfw	sertmp			; recall received serial data
		movwf	INDF0			; store in receive buffer
		incf	rcvptr,F		; increment receive buffer pointer
		movfw	rcvptr			; recall buffer pointer
		btfss	rcvbuf,0		; skip if an extended message
		addlw	.3				; adjust for extended commands being 5 bytes long
		sublw	.5				; has it received the complete message? (2 or 5 bytes)
		btfss	STATUS,Z		; skip to send checksum if entire message received
		return

;		Complete serial message has been received

		bcf		serflg,rcvrdy	; clear receive ready flag until next "55" sent
		bcf		serflg,rcving	; clear serial receive flag
		bsf		serflg,chkout	; set flag indicating checksum is being sent
		bsf		serflg,serxmt	; set flag to transmit checksum
		return

;		Process checksum acknowledge

chkack	btfss	serflg2,clkpnd	; skip if clock initialization is pending
		goto	srechk			; check for serial ring enable
		bcf		serflg2,clkpnd	; clear clock initialization pending flag
		goto	serrdy			; serial port is ready

srechk	btfss	serflg2,srepnd	; skip if serial ring enable is pending
		goto	srdchk			; check for serial ring disable
		bcf		serflg2,srepnd	; clear serial ring enable pending
		bcf		serflg2,srdpnd	; make sure serial ring disable pending is also cleared
		bsf		serflg2,rngena	; set serial ring enable
		goto	serrdy			; serial port is ready

srdchk	btfss	serflg2,srdpnd	; skip if serial ring disable is pending
		goto	serqtx			; begin X10 transmit if not ring enable or disable
		bcf		serflg2,srdpnd	; clear serial ring disable pending
		bcf		serflg2,rngena	; clear serial ring enable
		bsf		PORTC,RNGOUT	; make sure serial ring out is deasserted (low)
		goto	serrdy			; serial port is ready

;		Begin transmit if X10 transmit buffer is empty and powerline is clear

serqtx	bsf		serflg,rcvful	; indicate serial buffer is full and ready for transmit
		clrf	xmtagn			; make sure re-transmit count is cleared
		clrf	timout			; no timeout for next serial input
serx10	movfw	xmtcnt			; check if transmit buffer is empty
		btfss	STATUS,Z		; set up next transmit if so
		return

;		Delay transmit if powerline is not clear

		movf	dimcnt,F		; check if transmitting a bright/dim sequence
		btfss	STATUS,Z		; skip if not
		goto	ext2nd			; continue sending sequence without a gap
		movlw	.248			; to check for a 4 cycle gap
		addwf	gapcnt,W		; has there been a 4 cycle gap?
		btfss	STATUS,C		; set up transmit if so
		return		

ext2nd	call	packit			; convert serial message into X10 bit string
		bsf		stsflg,xmtmsg	; set flag to transmit X10 message
		return

;		Serial port error processing

serrst
		banksel	RCREG			; select BANK3 for serial port
		bcf		RCSTA,CREN		; pulse serial receiver off to clear overflow error
		bsf		RCSTA,CREN		; turn serial receiver back on
sererr
		banksel	RCREG			; select BANK3 for serial port
		movfw	RCREG			; make sure receive register is cleared
		clrf	BSR				; back to BANK0
		clrf	stsflg			; clear all status flags
		clrf	serflg			; clear all serial port flags
		clrf	dimcnt			; clear dim count for corrupted message
		clrf	chksum			; make sure checksum is cleared
		clrf	rcvptr			; make sure receive buffer pointer is cleared
		clrf	xmtptr			; make sure transmit buffer pointer is cleared
		bcf		serflg2,srepnd	; make sure serial ring enable pending is cleared
		bcf		serflg2,srdpnd	; make sure serial ring disable pending is cleared
		bsf		serflg,rcvrdy	; set receive ready flag
		movlw	ledser			; number of flashes to indicate serial link error
		call	ledset			; set LED count for number of flashes
		return


;		SERIAL DATA OUTPUT

;		This subroutine sends serial data to the RS232 port

serout	clrf	BSR				; select BANK0
		btfsc	serflg,serxmt	; skip if a serial transmit is not pending
		goto	serchk			; process serial output
		btfsc	serflg,rcving	; skip if not currently receiving a serial message
		return					; just return if so
		btfsc	serflg,chkout	; skip if not waiting for checksum acknowledge
		return					; just return if so

		btfss	serflg,xmtful	; skip buffer full & waiting for transmit
		return					; just return if not
		btfsc	serflg,serreq	; skip if output request flag is not yet set
		return					; just return if so
		btfss	serflg,rcvrdy	; skip & issue serial output request if ready
		return					; just return if not

;		Check if another command can be combined into this upload

		movfw	xmtbuf			; get number of bytes currently in transmit buffer (3 to 10)
		addlw	.246			; check if buffer is full 246+10 -> 256
		btfsc	STATUS,C		; skip if still more room in buffer
		goto	setreq			; set serial output request
		movfw	rcvcnt			; get received bit count
		addlw	.252			; (4 bits for valid start pattern)
		btfsc	STATUS,C		; skip if not receiving another command 
		return					; just return if receiving another command
		movf	reqdly,F		; check output request delay
		btfsc	STATUS,Z		; skip if still delay left
		return
		decfsz	reqdly,F		; tick off 8.33mS & set output request flag if delay over 
		return					; just return if still waiting for another command

setreq	bsf		serflg,serreq	; set serial output request flag

		movlw	.60				; reload count for next second
		movwf	seccnt
		bsf		serflg,serxmt	; set transmit pending flag
		goto	sertrq			; transmit serial output request

serchk	movfw	chksum			; get checksum
		btfsc	serflg,chkout	; skip if not output checksum
		goto	sertxw			; send checksum to serial transmit register
		movlw	H'55'			; to transmit "55" serial port ready
		btfsc	serflg,srdy55	; skip if not waiting to send "55" serial port ready
		goto	sertxw			; output serial port ready
		movlw	H'A5'			; to transmit "A5" clock initialization request
		btfss	serflg2,clkset	; skip if clock was already initialized
		goto	sertxw			; output clock initialization request

sertrq	movlw	H'5A'			; to transmit "5A" for output request
		btfss	serflg,serreq	; skip if output request is pending
		goto	serdat			; transmit data from buffer

;		Transmit byte passed in W register

sertxw
		banksel	TXREG			; select BANK3 for serial port
		movwf	TXREG			; output serial data
		clrf	BSR				; back to BANK0
		bcf		serflg,serxmt	; clear transmit pending flag
		bcf		serflg,srdy55	; also make sure flag to send "55" ready is cleared
		btfss	serflg2,clkpnd	; skip if clock is pending
		return					; done

		movlw	.30				; 1/2 second timeout for checksum acknowledge
		movwf	timout			; set timeout for checksum acknowledgement
		return

;		Transmit data in serial output register

serdat	movfw	xmtptr			; get transmit buffer pointer (0 to 9)
		addlw	xmtbuf			; add physical memory offset of buffer
		movwf	FSR0L			; set indirect pointer to transmit buffer
		clrf	FSR0H
		movfw	INDF0			; get serial data from transmit buffer
		movf	xmtptr,F		; check if pointing at byte count
		btfsc	STATUS,Z		; skip if not the byte count
		addlw	.255			; subtract 1 for byte count
		banksel	TXREG			; select BANK3 for serial port
		movwf	TXREG			; output serial data
		clrf	BSR				; back to BANK0
		incf	xmtptr,F		; increment transmit buffer pointer
		incf	xmtptr,W		; get buffer pointer +1
		subwf	xmtbuf,W		; is it to end of data in buffer?
		btfsc	STATUS,C		; skip to clear transmit buffer if so
		return

		bcf		serflg,xmtful	; clear transmit buffer full flag
		bcf		serflg,serxmt	; clear transmit pending flag
		clrf	xmtptr			; clear transmit pointer
		clrf	xmtbuf			; indicate transmit buffer is empty
		return


;		SUBROUTINES TO CONTROL 120KHZ PWM TRANSMITTER

;		Turn on 120KHz PWM transmitter

xmton
		banksel	PSTR1CON		; select BANK5 for PWM control
		btfsc	PSTR1CON,STR1C	; skip if PWM transmitter is not on yet
		goto	xmton1			; done if already on

;		Check for command storm or collision

		clrf	BSR				; back to BANK0
		bsf		stsflg,outbit	; set flag indicating a "1" will be transmitted
		btfsc	stsflg,colisn	; skip & turn on transmitter if no collision has occurred
		return					; return without turning transmitter on

		banksel	PSTR1CON		; select BANK5 for PWM control
		movlw	.6				; for 3.12uS pulsewidth
		movwf	CCPR1L			; set PWM duty cycle
		movlw	b'00001100'		; set PWM active high mode with single output
		movwf	CCP1CON			; set PWM mode
		clrf	PWM1CON			; no PWM delay between phases
		clrf	CCP1AS			; no PWM auto-shutdown
		movlw	b'00010100'		; to enable PWM P1C output at next PWM period
		movwf	PSTR1CON		; enable 120KHz PWM

xmton1	clrf	BSR				; back to BANK0
		return

;		Turn off 120KHz PWM transmitter

xmtoff
		banksel	PSTR1CON		; select BANK5 for PWM control
		clrf	PSTR1CON		; disable PWM P1C output (also indicates xmtr is off)
		clrf	CCP1CON			; shut off PWM
		clrf	BSR				; back to BANK0
		bcf		PORTC,PWM120	; make sure PWM output is a "0"
		return


;		 MAIN PROGRAM TIMING LOOP

;		 This loops through completely for each half cycle of the powerline

start	call	initiz			; call initialization sequence
		goto	main			; go to main timing loop

;		Disable Timer 1 and inhibit driver when zero-crossing is not detected

main00	clrf	BSR				; make sure BANK0 is selected
		bcf		T1CON,TMR1ON	; turn off Timer 1
		clrf	TMR1H			; clear Timer 1
		clrf	TMR1L

;		Main timing loop

main
		banksel	PSTR1CON		; select BANK5 for PWM control
		clrf	PSTR1CON		; disable PWM P1C output (also indicates xmtr is off)
		clrf	CCP1CON			; also make sure the PWM is off

		banksel	TRISA			; select BANK1 for port direction register
		bsf		TRISA,ZCDIN		; set ZCD to an input
		clrf	BSR				; back to BANK0
		banksel	IOCAF			; select BANK7 for interrupt on change
		movlw	b'00100000'		; to enable interrupt on change for ZCD
		movwf	IOCAP			; set interrupt enable for ZCD positive edge
		movwf	IOCAN			; set interrupt enable for ZCD negative edge
		clrf	IOCAF			; clear any existing IOC flag bits
		clrf	BSR				; back to BANK0
		bsf		INTCON,IOCIE	; ensure ZCD interrupt on change is enabled

mainzc	movfw	TMR1H			; get Timer 1 high byte
		btfsc	STATUS,Z		; skip if Timer 1 is running
		goto	mainzc			; loop & wait for zero-crossing if Timer 1 is not running

main01	call	serinp			; check for serial input while waiting for zero crossing
		movfw	TMR1H			; get Timer 1 high byte
		addlw	.10				; check for timer preload
		btfsc	STATUS,C		; skip if not
		goto	main02			; begin processing next half cycle if so

		addlw	.168			; 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 comparator and D/A converter are enabled

main02	lsrf	agclvl,W		; get current 1/2 AGC level
		movwf	temp1			; save in temp
		lsrf	temp1,F			; divide by 2
		lsrf	temp1,W			; divide by 2 again for a total of 8
		banksel	DACCON1			; select BANK2 for comparator and D/A
		movwf	DACCON1			; make sure D/A is set to current AGC level
		movlw	b'10000000'		; enable D/A converter and not the output test point

;	movlw	b'10100000'		; to enable D/A with test point output

		movwf	DACCON0			; make sure D/A converter is enabled
		movlw	b'10100100'		; enable comparator 1 to monitor X10 BPF input
		movwf	CM1CON0
		movlw	b'00010001'		; no interrupts, DAC reference, use C12IN1- input 
		movwf	CM1CON1
		clrf	BSR				; back to BANK0

;		Delay until beginning of X10 reception window

dlywin	call	serinp			; check for serial input while waiting for reception window
		movfw	TMR1H			; get Timer 1 high byte
		addlw	.7				; at beginning of X10 reception window?
		btfss	STATUS,C		; skip if so
		goto	dlywin			; wait for beginning of reception window

;	bcf		PORTC,RNGOUT	; turn diagnostic pulse on

;		Start X10 reception window

main04	clrf	TMR0			; clear Timer 0 at start of window
		clrf	cyccnt			; clear cycle count for next reception window

;		Wait for end of reception window 900uS after zero crossing

main05	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	.2				; past the X10 reception window?
		btfss	STATUS,C		; skip if so
		goto	main05			; loop until end of reception window

		movfw	TMR0			; read Timer 0 count (=> 40 cycles for logic 1)
		movwf	cyccnt			; save count for later processing

;	bsf		PORTC,RNGOUT	; turn diagnostic pulse off

;		Delay until timer overflow (end of X10 transmit window)

main06	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	main06			; loop until timer overflow

;		Shut off PWM & digital output at end of X10 window

		banksel	PSTR1CON		; select BANK5 for PWM control
		clrf	PSTR1CON		; disable PWM P1C output (also indicates xmtr is off)
		clrf	CCP1CON			; also make sure the PWM is off
		clrf	BSR				; back to BANK0
		bcf		PORTC,PWM120	; make sure PWM output is a "0"

		movf	ledcnt,F		; check if LED is being flashed
		btfss	STATUS,Z		; skip if not
		goto	main07			; leave LED status alone when flashing
		btfss	stsflg,xmtmsg	; leave LED on when transmitting a message
		bsf		PORTC,LEDRED	; turn off green LED

;		Process received data and set up next bit to be transmitted

main07	call	rcvinp			; process received data
		movfw	xmtcnt			; check for data in X10 transmit buffer
		btfsc	STATUS,Z		; skip if transmitting
		bcf		stsflg,xmtmsg	; make sure transmit message flag is cleared		
		movfw	xmtcnt			; get transmit bit count
		btfss	STATUS,Z		; skip if not transmitting a message
		call	xmtbit			; set up next transmit bit
		call	serout			; check for serial data output

		movf	xmtagn,F		; check for re-transmit
		btfsc	STATUS,Z		; skip if so
		goto	main10			; no re-transmit
		movlw	.248			; to check for a 4 cycle gap
		addwf	gapcnt,W		; has there been a 4 cycle gap?
		btfss	STATUS,C		; set up re-transmit if so
		goto	main10			; no re-transmit
		decfsz	xmtagn,F		; also no re-transmit if already at maximum re-try limit
		call	pakagn			; set up re-transmit		
		bsf		stsflg,xmtmsg	; set flag to transmit X10 message

;		Sample AGC (alternate window) 1.6 mS to 2.4 mS after zero crossing

main10	movfw	cyccnt			; get received bit cycle count
		addlw	.208			; C set if => 48 cycles of 120KHz in the window
		btfss	STATUS,C		; skip if a logic "1"
		goto	main11			; sample AGC if not receiving an X10 bit
		btfss	stsflg,rcverr	; skip & sample AGC if error receiving X10 command
		goto	main			; no AGC check if receiving valid X10 command

main11	call	serinp			; check for serial input while waiting for AGC sample point
		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.6 mS after the last zero crossing (at 8.16MHz clk)
		btfss	STATUS,C		; skip to begin AGC sample if so
		goto	main11			; loop if not
		movlw	.246			; sample AGC until 2.4 mS after the last zero crossing
		movwf	agcend			; set up AGC cycle end

;	bcf		PORTC,RNGOUT	; turn diagnostic pulse on

		call	agcloop			; run AGC loop

;	bsf		PORTC,RNGOUT	; turn diagnostic pulse off

		goto	main			; loop and wait for next zero crossing


;		AGC LOOP

;		Uses internal comparator reference to measure noise

agcloop decf	agclvl,F		; decrease AGC level by 1 count
		movfw	agclvl			; get current AGC level
		addlw	.248			; to check if .15V or higher (255 = 5V)
		movlw	.8				; to set AGC level to .15V minimum
		btfss	STATUS,C		; skip if the AGC level .15V or higher
		movwf	agclvl			; set AGC level to .15V minimum

		lsrf	agclvl,W		; get 1/2 current AGC level
		movwf	temp2			; save in temp
		lsrf	temp2,F			; divide by 2
		lsrf	temp2,W			; divide by 2 again for a total of 8
		banksel	DACCON1			; select BANK2 for comparator and D/A
		movwf	DACCON1			; set D/A to current AGC level
		movlw	b'10000000'		; enable D/A converter and not the output test point

;	movlw	b'10100000'		; to enable D/A with test point output

		movwf	DACCON0			; make sure D/A converter is enabled
		movlw	b'10100100'		; enable comparator 1 to monitor X10 BPF input
		movwf	CM1CON0
		movlw	b'00010001'		; no interrupts, DAC reference, use C12IN1- input 
		movwf	CM1CON1
		clrf	BSR				; back to BANK0
		clrf	TMR0			; clear timer noise count

agclp1	movfw	TMR1H			; get Timer 1 high byte
		addwf	agcend,W		; check for end of AGC window
		btfsc	STATUS,C		; continue AGC sample window if not
		goto	agclp2			; end AGC window & set AGC level to lower value
		movfw	TMR0			; read Timer 0 count (=> 8 cycles for too much noise)
		addlw	.248			; has it read 8 noise pulses?
		btfss	STATUS,C		; skip and check with higher AGC level if so
		goto	agclp1			; loop until 8 pulses or end of AGC window

;		Increase AGC level if over 8 noise pulses at this AGC level

		call	incagc			; increase AGC level by 1 DAC bit
		lsrf	agclvl,W		; get 1/2 current AGC level
		movwf	temp2			; save in temp
		lsrf	temp2,F			; divide by 2
		lsrf	temp2,W			; divide by 2 again for a total of 8
		banksel	DACCON1			; select BANK2 for comparator and D/A
		movwf	DACCON1			; set new D/A value
		clrf	BSR				; back to BANK0
		clrf	TMR0			; clear timer noise count
		goto	agclp1			; loop until 8 pulses or end of AGC window

agclp2	return					; done with AGC loop


;		Increase AGC level by 1 DAC bit

incagc	movfw	agclvl			; get current AGC level
		addlw	.8				; increase AGC level by .15V
		movwf	agclvl			; save new value
		addlw	.52				; check if already 4V
		movlw	.204			; for maximum AGC level of 4V
		btfsc	STATUS,C		; skip if less than 4V
		movwf	agclvl			; set AGC to 4V maximum
		return


;		Diagnostic to output a series of pulses to TP1 equal to the value passed in W

tstpls	clrf	BSR				; make sure BANK0 is selected
		movwf	temp2			; save pulse count at temp
		incf	temp2,W			; to insure 1 pulse minimum
		btfss	STATUS,Z		; skip if overflow
		movwf	temp2			; save pulse count +1

tstplp	bsf		PORTA,LEDGRN	; output a positive going pulse
		bcf		PORTA,LEDGRN
		decfsz	temp2,F			; done?
		goto	tstplp			; loop if not
		return


;		EEPROM DATA

EE		CODE	0xF000			; (location found by trial and error)
		DE		"XTB-232_V1.05 "; 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

		END						; directive 'end of program'

