;**************************************************************************
; DGPS.ASM  for a 16F84		Version 1.0
;  
; This program runs the DGPS receiver.
; Copyright (c) 1998, Jim Bixby. All rights reserved
; This program can be freely used for non-commercial purposes, and
; carries no warranty of any kind, including any implied warranty
; of fitness for any particular purpose.
; Acknowledgement and thanks to Rich Heineck for the code for the 
; parity calculation and word sync algorithm.
;**************************************************************************

        	LIST P=16F84, R=DEC 
		INCLUDE	<P16F84.INC>
        	__CONFIG	_WDT_OFF & _XT_OSC
		ERRORLEVEL -302		;kill the annoying bank select message
;--------------------------------------------------------------------------
;External connections equates
;--------------------------------------------------------------------------
;PORTA pin assignments
;--------------------------------------------------------------------------

#define		SC104_Pin	PORTA,0		;SC104 output
INVERTED_104	EQU	0			;1 if SC-104 output is thru an inverter

#define		Synth_Clk	PORTA,1		;Shift clock for the PLL
#define		LCD_RS		PORTA,2		;RS pin on parallel LCD display
#define		Select4800	PORTA,2		;Jumper input read at init time --
						;selects the SC-104 output baud rate
						;0: 9600 baud     1:4800 baud
#define		Select4800_Bit	TRISA,2		;used to reverse direction of IO pin
#define		LCD_E		PORTA,3		;E pin on parallel LCD display

#define		Synth_Enb	PORTA,4		;ENB pin on the PLL

; These defines establish the output signal levels

#define	Synth_Enb_High		bsf	Synth_Enb
#define Synth_Enb_Low		bcf	Synth_Enb

#define	Synth_Clk_High		bsf	Synth_Clk
#define	Synth_Clk_Low		bcf	Synth_Clk

#define	Synth_Data_High		bsf	Synth_Data
#define	Synth_Data_Low		bcf	Synth_Data
		PAGE
;--------------------------------------------------------------------------
;PORTB pin assignments
;--------------------------------------------------------------------------


#define		CarrierVCO	PORTB,0		;FM demod vco input - used for
						;checking whether it is locked
#define		Detector	PORTB,1		;Detector output signal from
						;the receiver.  It is the limited
						;input carrier from IF2.  Used
						;for checking carrier PLL lock
#define		Synth_Data	PORTB,1		;Serial data to PLL
#define		Synth_Data_Bit	TRISB,1		;used to reverse direction of pin

#define		CarrierDet	PORTB,2		;Low if receiver detects
						;a carrier
#define		SynthLock	PORTB,3		;Low if freq synth is locked



#define		RcvrData	PORTB,4		;Serial data input from receiver
#define		SkipIfDataOne	btfss	Bit,4
#define		SkipIfDataZero	btfsc	Bit,4


#define		ModeSwitch	PORTB,5		;'MODE' pushbutton - not used
						;    pressed = high
#define		DownSwitch	PORTB,6		;'DOWN' pushbutton - pressed = high
#define		UpSwitch	PORTB,7		;'UP' pushbutton - pressed = high

SwMask		EQU	B'11000000'		;Where there are switches on PORTB
DataMask	EQU	B'00010000'		;Where RcvrData is on PORTB

		PAGE
;--------------------------------------------------------------------------
;Serial Output and LCD Equates
;--------------------------------------------------------------------------
K9600		EQU	30	;for 9600 baud delay
K4800		EQU	65	;for 4800 baud delay


		IF INVERTED_104
		#define SC104_MARK		bsf	SC104_Pin
		#define SC104_SPACE		bcf	SC104_Pin
		ELSE
		#define SC104_MARK		bcf	SC104_Pin
		#define SC104_SPACE		bsf	SC104_Pin
		ENDIF

		;the above #defines set up to transmit on a line
		;where the quiescent value is low, like a PC
		;would want on its COM input line

DispRow1	EQU	128	;base address of row 1
DispRow2	EQU	192	;base address of row 2

FreqPos		EQU	0	;Freq starts in first char of first row
FreqWidth	EQU	3	;Three digits used to display frequency
IDLabelPos	EQU	6	;"ID" label starts in char 7, row 1
StatPos		EQU	9	;Status identifiers start in 10th char

;The seven sync/lock status positions on the LCD must stay in the order
;specified below, or LCD updating will get screwed up
SynthStatPos	EQU	9	;Synthesizer Status
CarrierDetPos	EQU	10	;Carrier Detect
CarrierStatPos	EQU	11	;Carrier Lock status
BitSyncPos	EQU	12	;Bit Sync Lock status
WordSyncPos	EQU	13	;Word Sync status
FrameSyncPos	EQU	14	;Frame Sync Status
ParityPos	EQU	15	;Parity Good/Error
BitRatePos	EQU	0	;bit rate starts in first char of second row
BitRateWidth	EQU	2	;two digits used to display bit rate
				;(the trailing 0 is fixed)
IDPos		EQU	5	;Station ID starts in char 6 row 2
IDWidth		EQU	3	;three digits used to display Station ID

GoodChar	EQU	"+"	;char for "good" status
BadChar		EQU	"X"	;     and for bad status

		PAGE
;--------------------------------------------------------------------------
; Processor equates
;--------------------------------------------------------------------------
		#define _C	STATUS,0
		#define _Z	STATUS,2
;--------------------------------------------------------------------------
; Bit, Word and Frame Synchronizer Equates
;--------------------------------------------------------------------------
BitWidth	EQU	78	;one bit width
SyncCenter	EQU	38	;middle of bit
ConfWindow	EQU	16	;TMR0 counts either side of middle, to say
				;a bit edge was "good" instead of "bad"
Phase_Limit	EQU	7	;max amount to tweak TMR0 on each
				;detected bit edge, if out of sync
				;(if in sync, only steps of 1 are made)

BitSyncUp	EQU	1	;Up and Down count values
BitSyncDown	EQU	-7	;for Bit Sync Lock confidence register
WordSyncUp	EQU	16
WordSyncDown	EQU	-64
FrameSyncUp	EQU	64
FrameSyncDown	EQU	-120
CarrierSyncUp	EQU	4
CarrierSyncDown	EQU	-32

Preamble	EQU	B'01100110'	;Preamble marking start of frame

;where the parity bits are in received data register Data_A
D25		EQU	7
D26		EQU	6
D27		EQU	5
D28		EQU	4
D29		EQU	3
D30		EQU	2

;where D29 and D30 from the previous word are, in Data_E
D29P		EQU	1
D30P		EQU	0

;--------------------------------------------------------------------------
; Other equates
;--------------------------------------------------------------------------
IF1		EQU	122	;First IF is at 122 KHz
IF2		EQU	3	;Second IF is at 3 KHz
LO1_Base	EQU	285+IF1	;First-LO freq for lower end of beacon band
Synth2		EQU	1000	;Second divider in synth used to make
				;1 khz reference from 1 MHz
AuxRefCntr	EQU	0x4FA0	;This sets the AuxRefCntr to zero (the actual
				;value is a don't-care, since we don't use it)
				;and tells the PLL to put the output of the
				;12-bit Ref div-by-n counter on fR1
RefRegCntl	EQU	0x3000	;'OR' this value into the Ref div-by-n value
				;to tell the PLL Phase detectors to 
				;listen to fR1 (see MC145162 data sheet)
RefFreq		EQU	4000+RefRegCntl	
				;Reference Freq in KHz, plus the
				;control bits
PLL_Cntl	EQU	0xA0	;PLL control word -- sets Test Mode, turns off
				;ADin pin, sets RefOut to div-by-4
		PAGE
;--------------------------------------------------------------------------
; Variables
;--------------------------------------------------------------------------
 cblock	0x0C
		LO1:2		;Two bytes for first LO div-by-n value
		Chan		;Current Channel Number

		BitSync		;Bit Sync Lock confidence counter.  Count
				;up by SyncUpCount to max of 255 when good bit 
				;sync phase is detected, down by SyncDownCount 
				;when bad bit sync phase detected.
		BitRate		;1:200bps, 2:100bps, 3:50bps
		OldBitRate	;Last bit rate displayed.
		WordSync	;Word Sync Confidence counter, like BitSync
		FrameSync	;Frame Sync Confidence Counter, like BitSync
		CarrierSync	;Carrier Sync Confidence Counter, like BitSync
		Bit		;Value of last bit received

		Flags		;1-bit Flags registers, for various things
		Flags2		;This one is *not* cleared during init.
		OldSync		;Flag bits for the previous sync state
		CurrSync	;Flag bits for the current sync state

		LCD_char	;buffer to hold a char to send to the LCD
		Bit_K		;holds delay count for SC104 output
		SC104_cntr	;counter for sending out the SC104 char
		SC104_char	;buffer to hold a char to send to SC104 out
		SC104_in	;accum to capture the six incoming bits
		SC104_in_cntr	;where we count up the six incoming bits

		Data_E		;Registers for the parity algorithm and
		Data_D		;word sync.  Data_X holds the incoming
		Data_C		;stream and it is shifted on every incoming
		Data_B		;bit
		Data_A	
		ThisWord_D	;ThisWord_X holds the sync'd 24bit
		ThisWord_C	;data word, polarity corrected.  When in
		ThisWord_B	;sync, it is changed only every 30 bit word
		Parity		;Where parity is computed
		PriorWord_D
		PriorWord_C
		PriorWord_B
		Data_Count	;Counts bits shifted into Data_X

		SeqNum		;Sequence number read from input data
		FrameLength	;Frame length read from input data
		WordNumber	;Which word we just received in the frame
				;   Word 0 is the one with the preamble
				;   Counts up from there
		StaID:0		;Two bytes for Station ID, from input data
		StaID_MSB	;   Most significant byte
		StaID_LSB	;   Least significant byte
		OldStaID:0	;Last Station ID put in LCD
		OldStaID_MSB
		OldStaID_LSB

		RegA		;used by the BCD converter
		RegB
		RegC

		temp		;temporary register used in interrupt handler
		tempbg		;temporary registers used in background routine
		delay_cntr1	;used in delay routines
		delay_cntr2

		Save_W		;used by the interupt handler
		Save_Status
		Save_FSR
 endc

;Flags definitions:

#define		BadSyncFlag	Flags,0		;1 if a BitEdge is detected
						;out of sync zone
#define		SC104Flag	Flags,1		;1 to request a char send
						;on the SC104 out port
#define		LCDFlag		Flags,2		;1 to request a char send
						;to the LCD


#define		LCDFreqUpdRqst	Flags,4		;rqst from int handler to
						;background to update the LCD
						;Freq display

#define		GoodFSTest	Flags,6		;1 if FS test criteria met on a test

#define		TrialWordSync	Flags2,1	;1 if we have found good parity
						;and want to keep testing this position
#define		TrialFrameSync	Flags2,2	;1 if we have found the preamble,
						;and want to keep testing this position
#define		RS		Flags2,3	;0 to send a command to the LCD
						;1 to send a character to display




;CurrSync and OldSync flags registers definitions
;These must stay in the order below, or LCD updating will get screwed up
SynthFlag	EQU	0
CarrierDetFlag	EQU	1
CarrierFlag	EQU	2
BitSyncFlag	EQU	3
WordSyncFlag	EQU	4
FrameSyncFlag	EQU	5
GoodParityFlag	EQU	6


;-------------------------------------------------------------------------
;Compute how much data memory is left
;-------------------------------------------------------------------------
 cblock
		EndMem:0	;define the end of memory
 endc

;-------------------------------------------------------------------------
;Data EEPROM initialization
;-------------------------------------------------------------------------
		ORG	0x2100		;where data eeprom starts

VERSION		de	"Ver 1.0 DGPS.ASM"
		de	"copyright Jim Bixby 1998"

LastChan	de	17		;last channel the receiver was
					;  locked to.  It is the  kHz offset
					;  from 285kHz (Chan 17 is 302 kHz,
					;  the  Point Loma frequency)
EndData		de	0		;last free byte of data EEPROM

;-------------------------------------------------------------------------
; Calculate remaining file space and data eeprom, for reference
;-------------------------------------------------------------------------
MemLeft		EQU	0x50-EndMem		;number of files registers unused
EELeft		EQU	0x2140-EndData		;amount of data eeprom unused
		PAGE

                ORG     0x000   
		goto	Start

		ORG	0x004
		goto	int

;--------------------------------------------------------------------------
; Parity_Tbl -- determines the parity of the nibble passed in W.
;	**** The upper nibble must be zero *******
;	Returns in W: 
;		0: even parity
;		1: odd parity
;--------------------------------------------------------------------------
Parity_Tbl	addwf	PCL,f
		retlw	0	;0000
		retlw	1	;0001
		retlw	1	;0010
		retlw	0	;0011
		retlw	1	;0100
		retlw	0	;0101
		retlw	0	;0110
		retlw	1	;0111
		retlw	1	;1000
		retlw	0	;1001
		retlw	0	;1010
		retlw	1	;1011
		retlw	0	;1100
		retlw	1	;1101
		retlw	1	;1110
		retlw	0	;1111
;-----------------------------------------------------------------------------
;The next two tables return the first and second Bit Rate characters
;   Enter with the bit rate in W
;-----------------------------------------------------------------------------
BR_First	
		addwf	PCL,1
		retlw	" "		;bit rate can't be zero
		retlw	"2"
		retlw	"1"
		retlw	" "

BR_Second	
		addwf	PCL,f
		retlw	" "
		retlw	"0"
		retlw	"0"
		retlw	"5"

;------------------------------------------------------------------------------
; ChkParity -- a MACRO
;
;   Verify the specified parity bit matches the parity of the masked 24 data 
;   bits and previous "polarity" bit. Parity calculation is done a nibble 
;   at a time using a lookup table.
;
;   Inputs:
;	Mask	3 byte mask to apply to 24-bit data word
;	ParBit	The parity bit being tested
;	PolBit	D29' or D30' from previous word
;
;   Return:
;	Jumps to 'badparity' if parity test fails
;	Falls through if parity matches
;------------------------------------------------------------------------------
ChkParity	MACRO	Mask, ParBit, PolBit
		LOCAL	even, match
		clrf	Parity			; Parity "accumulator". Initialize
		btfsc	Data_E,PolBit		;  it with D29 or D30 from the
		incf	Parity,f		;  previous frame
		swapf	ThisWord_D,w		; Begin with new block d1..d4
		andlw	(Mask >> 20) & 0x0F	; Bits go in low nibble of W
		call	Parity_Tbl
		xorwf	Parity,f		; Parity ^= W
		movf	ThisWord_D,w		; d5..d8
		andlw	(Mask >> 16) & 0x0F
		call	Parity_Tbl
		xorwf	Parity,f
		swapf	ThisWord_C,w		; d9..d12
		andlw	(Mask >> 12) & 0x0F
		call	Parity_Tbl
		xorwf	Parity,f
		movf	ThisWord_C,w		; d13..d16
		andlw	(Mask >> 8) & 0x0F
		call	Parity_Tbl
		xorwf	Parity,f
		swapf	ThisWord_B,w		; d17..d20
		andlw	(Mask >> 4) & 0x0F
		call	Parity_Tbl
		xorwf	Parity,f
		movf	ThisWord_B,w		; d21..d24
		andlw	Mask & 0x0F
		call	Parity_Tbl
		xorwf	Parity,f
		bz	even			; Test parity, skip next if 1
		btfss	Data_A,ParBit		; Odd parity. Verify parity bit matches.
		goto	badparity
		goto	match
even		btfsc	Data_A,ParBit		; Even. Verify parity bit matches.
		goto	badparity
match
		ENDM	
		PAGE
;--------------------------------------------------------------------------
; Interrupt service routines
;--------------------------------------------------------------------------

int		movwf	Save_W			;interrupted -- save W
		movfw	STATUS			;and STATUS
		movwf	Save_Status
		movfw	FSR			;save the FSR
		movwf	Save_FSR

		btfss	INTCON,INTE		;is the CarrierVCO int enabled?
		goto	_int_chk_tmr		;  no-go check other sources	
		btfss	INTCON,INTF		;  yes - was that the int?
		goto	_int_chk_tmr		;    no

;here to service an RB0/INT interrupt -- do one measurement on the lock
;status of the Carrier PLL VCO
		movlw	CarrierSync		;set up for a confidence test
		movwf	FSR			;on CarrierSync
		movlw	CarrierSyncUp		;assume a good test
		btfsc	Detector		;and skip if it was good
		movlw	CarrierSyncDown		;get the 'bad' test value
		call	ConfCntr		;go do the test
		btfss	_C			;skip if test finished
		goto	_exit_carrier

	;we have determined carrier lock status -- set the flag
	;accordingly
		bsf	CurrSync,CarrierFlag	;assume it passed
		btfsc	_Z			;and skip if it did
		bcf	CurrSync,CarrierFlag	;no - it failed

_exit_carrier	bcf	INTCON,INTE		;disable another interrupt
		goto	_int_restore



_int_chk_tmr	
		btfss	INTCON,T0IF		;see if it was the Timer (bit interval)
		goto	_int_chk_edge		;no, go see if a bit edge or switch
						;press happened

	;Interrupt handler for TMR0, the bit interval timer
		movlw	-BitWidth		;reload the timer
		movwf	TMR0
		bcf	INTCON,T0IF		;clear the flag

		;sample and process the incoming bit
		;send them out, six at a time

		movfw	PORTB			;read the bit
		andlw	DataMask		;and save it, in position, in
		movwf	Bit			;Bit
		bcf	_C			;clear the carry prior to shifting
		rrf	SC104_in,f		;make room for the new bit
		movlw	0x20			;this is where the new bit goes
		SkipIfDataZero			;skip if the received bit is zero
		iorwf	SC104_in,f		;if 1, set the bit

		decfsz	SC104_in_cntr,f		;see if we have 6 bits yet
		goto	Parity_Test		;not yet -- go calculate parity

	;we have received six bits -- send them out the SC104 port
		movfw	SC104_in		;set bit six and
		iorlw	0x40			;send the char
		movwf	SC104_char
		bsf	SC104Flag		;tell the handler to send it
		movlw	6			;reset the counter
		movwf	SC104_in_cntr		;and clear the bit accumulator
		clrf	SC104_in

		PAGE
;--------------------------------------------------------------------
; Do the parity calculation and set/maintain WordSyncFlag
;--------------------------------------------------------------------
Parity_Test	movlw	-1			;put the received bit
		addwf	Bit,w			;into Carry
		rlf	Data_A,f		;then shift into Data
		rlf	Data_B,f
		rlf	Data_C,f
		rlf	Data_D,f
		rlf	Data_E,f

		;If we are out of sync, or 30th bit was shifted in
		;test the parity to see if it is good
		btfss	TrialWordSync	;if not trying this position
		goto	dotest			;go test on every bit
		decfsz	Data_Count,f		;else, only test on 30th bit
		goto	FSDone			;no test required at this time
						;and not time to test FS either
dotest		movlw	30			;reset the bit counter
		movwf	Data_Count	

		;save data from prior word, then
		;make copy of data, with correct polarity
		movfw	ThisWord_B
		movwf	PriorWord_B
		movfw	ThisWord_C
		movwf	PriorWord_C
		movfw	ThisWord_D
		movwf	PriorWord_D

		movfw	Data_B
		movwf	ThisWord_B
		movfw	Data_C
		movwf	ThisWord_C
		movfw	Data_D
		movwf	ThisWord_D

		btfss	Data_E,D30P		;use previous word's D30
		goto	dopar			;to invert data
		comf	ThisWord_B,f
		comf	ThisWord_C,f
		comf	ThisWord_D,f

	; Calculate parity using the signal spec algorithm and compare to 
	; each of the parity bits. Note: A steady stream of 0's will pass 
	; the parity test.
dopar
			EXPAND
		ChkParity	0xEC7CD2,D25,D29P
			NOEXPAND
		ChkParity	0x763E69,D26,D30P
		ChkParity	0xBB1F34,D27,D29P
		ChkParity	0x5D8F9A,D28,D30P
		ChkParity	0xAEC7CD,D29,D30P
		ChkParity	0x2DEA27,D30,D29P

	; Now make sure everything's not all 0's or 1's
		movfw	Data_A
		iorwf	Data_B,w
		iorwf	Data_C,w
		iorwf	Data_D,w
		btfsc	_Z
		goto	badparity
		movfw	Data_A
		andwf	Data_B,w
		andwf	Data_C,w
		andwf	Data_D,w
		xorlw	0xFF
		btfsc	_Z
		goto	badparity

	; We get here if the parity check passed
		bsf	CurrSync,GoodParityFlag
		btfss	TrialWordSync	;if we are trying this position, run
					;the confidence counter
		goto	start_trial	;else go set flag to try this position
					;30 bits from now
		movlw	WordSync	;point FSR at the WordSync conf. counter
		movwf	FSR
		movlw	WordSyncUp
		call	ConfCntr	;go count the counter up
		btfsc	_C
		bsf	CurrSync,WordSyncFlag	;overflow - say we are in sync
		goto	WSDone

start_trial	
		bsf	TrialWordSync		;show that we want to try this spot
DropSync	movlw	0x80			;re-init the confidence counters
		movwf	WordSync
		movwf	FrameSync
		bcf	CurrSync,WordSyncFlag	;can't possibly be in word sync now
		bcf	CurrSync,FrameSyncFlag	;or in frame sync
		bsf	StaID,7			;mark station ID as invalid
		goto	WSDone

	; We get here if the parity test failed
badparity	bcf	CurrSync,GoodParityFlag
		btfss	TrialWordSync		;skip if we testing a trial location
		goto	WSDone			;no, exit

	; We get here if are testing a trial position, but a bad parity test
		movlw	WordSync		;so run the confidence counter
		movwf	FSR
		movlw	WordSyncDown
		call	ConfCntr		;go count the counter down
		btfss	_C			;if inbounds, exit
		goto	WSDone
		bcf	TrialWordSync		;underflow: setup to start hunting anew
		goto	DropSync

WSDone
		PAGE
;---------------------------------------------------------------------
; Frame synchronization code, to read the station ID
;---------------------------------------------------------------------		
FrSync		btfss	CurrSync,WordSyncFlag	;if not in word sync
		goto	FSDone			;don't try frame sync


		incf	WordNumber,f
		btfss	TrialFrameSync		;are we trying a position?
		goto	FSNotTrying		;no

	;we are testing a location -- see if now is the time
		movfw	WordNumber
		xorwf	FrameLength,w
		btfss	_Z
		goto	FSDone			;nope - don't check

		incf	SeqNum,f		;right time, so inc. seq num

		movlw	Preamble		;see if the preamble is there
		xorwf	PriorWord_D,w
		btfss	_Z
		goto	BadFS			;nope - test failed
		movfw	ThisWord_C		;compare seq numbers
		xorwf	SeqNum,w			
		andlw	0x07
		btfss	_Z
		goto	BadFS			;nope - test failed
		bsf	GoodFSTest		;remember this test passed
		movlw	FrameSyncUp		;passed - go up
		goto	DoFSConf
BadFS		bcf	GoodFSTest
		movlw	FrameSyncDown
		bsf	StaID,7
DoFSConf	movlw	FrameSync		;do the conf count
		movwf	FSR
		call	ConfCntr
		btfss	_C			;over/underflow?
		goto	Exit_FS			;no - finished for now
		btfsc	_Z			;skip if overflow
		goto	FSReject		;underflow-reject this spot
		bsf	CurrSync,FrameSyncFlag	;success - show Frame Sync
		goto	Exit_FS			;and do the wrap up

FSReject	bcf	CurrSync,FrameSyncFlag	;out of sync
		bcf	TrialFrameSync		;start all over
		movlw	0x80			;reset conf counter
		movwf	FrameSync
		bsf	StaID,7			;not in sync, so StaID is bad
		goto	FSDone

FSNotTrying	movlw	Preamble
		xorwf	PriorWord_D,w		;Preamble?
		btfss	_Z
		goto	FSDone			;no-exit

		movlw	0x80			;init conf counter
		movwf	FrameSync
		bsf	StaID,7
		bsf	TrialFrameSync		;show we want to test this spot
Exit_FS		clrf	WordNumber		;this is word 0
		movfw	ThisWord_C		;save sequence number
		andlw	0x07
		movwf	SeqNum
		movfw	ThisWord_B		;and FrameLength
		movwf	FrameLength
		rrf	FrameLength,f
		rrf	FrameLength,f
		rrf	FrameLength,w
		andlw	0x1F
		addlw	2
		movwf	FrameLength		;this is when the
						;next start of frame is
		bsf	StaID,7
		btfss	CurrSync,FrameSyncFlag	;if in sync
		goto	FSDone
		btfss	CurrSync,GoodParityFlag	;and good parity
		goto	FSDone
		btfss	GoodFSTest		;and the FS criteria was met
		goto	FSDone
		movfw	PriorWord_B		;save the Station ID
		movwf	StaID_LSB
		movfw	PriorWord_C
		andlw	0x03
		movwf	StaID_MSB
		goto	FSDone

FSDone
		PAGE
;---------------------------------------------------------------------
; Serial output routine.
;	To send a char to the LCD, put the char in LCD_char.
;		For a control char, clear flag RS
;		For a char to be displayed, set flag RS
;		Then set flag LCD_char, and wait until this
;		routine comes along, sends the char, and clears
;		the flag.  LCDFlag is set *only* in the background
;		code.
;	To send a char to the SC104 serial out port, put the
;		char in SC104_char and set flag SC104Flag.  After
;		sending, this routine clears SC104Flag.  SC104Flag
;		is set *only* by the TMR0 interrupt service routine.
;	The SC104 baud rate is determined by Bit_K, which in turn is
;		set during Init, after looking at the 4800/9600 jumper
;		on the receiver board.
;---------------------------------------------------------------------

SerOut
		btfss	LCDFlag		;LCD character waiting?
		goto	_chk_SC104	;no, go check for SC104 output

		BANKSEL	TRISB		;switch port to output;
		clrf	TRISB
		BANKSEL	PORTB

		bcf	LCD_E		;make sure E is low, and set RS
		bcf	LCD_RS		;appropriately
		btfsc	RS
		bsf	LCD_RS
lcdsend		movfw	LCD_char	;output the char
		movwf	PORTB
		bsf	LCD_E		;and strobe the E pin
		bcf	LCD_E

		BANKSEL	TRISB		;and switch the port back to inputs
		comf	TRISB,f
		BANKSEL	PORTB

		bcf	LCDFlag		;note that the char has been sent


_chk_SC104	btfss	SC104Flag	;if SC104 flag set, send a char
		goto	SerDone		;nothing to send

_do_serial
		movlw	9		;8 data + start
		movwf	SC104_cntr

		SC104_SPACE		;set the start bit
		goto	$+1		;equalize delay paths
		goto	$+1
		nop

_baud_delay	movfw	Bit_K		;baud delay timer
		movwf	temp


_ser_loop	decfsz	temp,f		;wait one baud
		goto	_ser_loop
		decfsz	SC104_cntr,f	;count down one bit
		goto	_next_bit
		goto	_SC104_Done

_next_bit	rrf	SC104_char,f	;put the bit to send in the carry
		btfsc	_C
		goto	_SC_Mark	;send a Mark for a 1
		SC104_SPACE
		nop			;delay equalizer
		goto	_baud_delay
_SC_Mark	SC104_MARK
		goto	_baud_delay

_SC104_Done	goto	$+1
		nop
		bcf	SC104Flag	;note char has been sent
		goto	$+1
		SC104_MARK		;send the stop bit

SerDone		

		PAGE
;--------------------------------------------------------------------------	
;_int_chk_edge: Interrupt handler for switch presses or a bit edge received
;--------------------------------------------------------------------------		

_int_chk_edge	btfss	INTCON,RBIF		;see if switch press or bit edge
		goto	exit_int		;nope -- exit the handler

		;switch or bit edge -- check bit edge first
		movfw	PORTB			;read RcvrData
		andlw	DataMask		;pick out the right bit
		xorwf	Bit,w			;xor with last bit received -- result
		btfsc	_Z			;non-zero if bit changed
		goto	_int_chk_switch		;no change: go check for a switch press

	;Interrupt handler for BitEdge - just process the edge event, not the bit

		bcf	BadSyncFlag		;start off assuming sync is good
		movlw	BitSync			;point FSR at the BitSync conf cntr
		movwf	FSR

		movfw	TMR0			;read the timer
		addlw	SyncCenter		;check the phase
		movwf	temp			;and save the error
		btfss	_C			;skip if phase lagging
		goto	_leading

		; phase is lagging or exact -- temp has the phase error
		btfsc	_Z			;if exact, do nothing
		goto	_end_adjust

		; Adust timer, proportional to error value (but not more than three)

		addlw	-Phase_Limit-1		;See if outside of PhaseLimit
		btfss	_C			;skip if so
		goto	_lag_adj		;otherwise, just go adjust
		movfw	temp			;get the error back
		addlw	-ConfWindow
		btfsc	_C			;if >Window, declare
		bsf	BadSyncFlag		;BitEdge out of bounds
		movlw	Phase_Limit		;Clip to adjustment limit
		movwf	temp
		
_lag_adj	movfw	temp
		btfsc	CurrSync,BitSyncFlag	;only move one if in sync
		movlw	1
		subwf	TMR0,f
		goto	_end_adjust

_leading
		; phase is leading and outside of zone -- increment timer
		; temp has the phase error, and is negative
		comf	temp,f			;make positive
		incf	temp,f
		movfw	temp			;clip at Phase_Limit
		addlw	-Phase_Limit-1
		btfss	_C
		goto	_lead_adj
		movfw	temp
		addlw	-ConfWindow
		btfsc	_C			;if >Window, declare
		bsf	BadSyncFlag		;BitEdge out of bounds
		movlw	Phase_Limit
		movwf	temp

_lead_adj	movfw	temp			;and adjust the phase
		btfsc	CurrSync,BitSyncFlag	;but only one if in sync
		movlw	1
		addwf	TMR0,f

		;done with phase adjustment -- update sync confidence counter
_end_adjust	movlw	BitSyncUp		;get amount to move counter
		btfsc	BadSyncFlag
		movlw	BitSyncDown
		call	ConfCntr		;and go move it
		btfss	_C			;Carry means under/overflow
		goto	_exit_BitEdge
		btfss	_Z			; Z means underflow: out of sync
		goto	_into_bitsync

		;Test ended, and concluded no sync to be found
		bcf	CurrSync,BitSyncFlag	;declare out of bit sync
		decfsz	BitRate,w		;Move to next rate
		goto	$+2
		movlw	3
		movwf	BitRate			;rollover, back to 50 bps
		call	SetBitRate		;go set the next rate to try
		goto	_exit_BitEdge


_into_bitsync	bsf	CurrSync,BitSyncFlag	;declare sync

_exit_BitEdge

		;fall into checking the pushbutton switches
		PAGE
;---------------------------------------------------------------------------
; _int_chk_switch: Interrupt handler for pushbutton presses
; (The Mode switch is ignored and is available for possible future
; expansion of the code)
;---------------------------------------------------------------------------
_int_chk_switch	movlw	41			;maximum channel number plus 1
		btfss	UpSwitch		;see if UP pressed
		goto	_checkDown		;nope
		call	UpOneChan		; UP pressed -- increment Chan
		goto	_newchan

_checkDown	btfss	DownSwitch		;check the DOWN switch
		goto	exit_int		;nope, exit
		call	DownOneChan		; DOWN pressed -- decrement Chan

	; Now, update the PLL and LCD with the new channel number
_newchan	call	Set_PLL
		bsf	LCDFreqUpdRqst		;Ask the background to display
						;the new frequency

	; Wait for switch to be released
_swloop		movfw	PORTB
		andlw	SwMask			;all switches should be off
		btfss	_Z			;skip if they are
		goto	_swloop			;and test again if not
		movlw	4			;wait 40 ms
		call	Delay_Long
		movfw	PORTB
		andlw	SwMask			;all switches should be off
		btfss	_Z			;skip if they are
		goto	_swloop			;and test again if not
		PAGE
;----------------------------------------------------------------------------
; exit_int -- to exit the interrupt handler
;----------------------------------------------------------------------------
exit_int	bsf	INTCON,RBIE		;make sure switch ints enabled
		bcf	INTCON,RBIF		;and clear the source flag
		bsf	INTCON,INTE		;enable a Carrier interrrupt next

_int_restore	movfw	Save_FSR
		movwf	FSR
		swapf	Save_W,f		;tricky move to restore W
		movfw	Save_Status		;get the saved status
		movwf	STATUS			;and restore it
		swapf	Save_W,w		;restore W w/o affecting Z
						;of status
		bcf	INTCON,INTF		;clear Carrier int. flag
		retfie
		PAGE
;----------------------------------------------------------------------------
; Some routines called from the interrupt handler
;----------------------------------------------------------------------------
DownOneChan	decf	Chan,f
		btfss	Chan,7			;see if sign went negative
		return				;nope, all done
		movlw	40
		movwf	Chan			;underflow -- wrap around to 40
		return

UpOneChan	incf	Chan,f
		subwf	Chan,w			;test for over max (41 is in w)
		btfsc	_Z			;skip if not over
		movwf	Chan			;went over; wrap around to zero
		return
;--------------------------------------------------------------------------------
; ConfCntr -- generalized confidence counter routine
;		Enter with FSR pointing to the counter to be updated
;		and the amount to change in W (signed byte)
;		Result is returned in the Zero and Carry bits in the
;		status register as follows:
;			Carry clear: counter stayed above 0 and below 255
;
;				     Zero bit indeterminant
;
;			Carry set:   counter overflowed (tried to go over 255) or
;				     counter underflowed (tried to go below 0)
;
;				     Zero set: Underflow -- counter set to 0
;				     Zero clear: Overflow -- counter set to 255
;---------------------------------------------------------------------------------
ConfCntr	movwf	temp		;save delta so we can remember
					;the direction we moved
		addwf	INDF,f		;update the counter
		btfss	temp,7		;skip if we moved down
		goto	_conf_up

		btfsc	_C		;going down: no carry means underflow
		goto	_inbounds
		clrf	INDF		;zero the counter (also sets Z:underflow)
		bsf	_C		;set carry to show over/underflow
		return

_conf_up	btfss	_C		;going up: carry means overflow
		goto	_inbounds
		clrf	INDF		;set counter to 255
		comf	INDF,f		; (also clears Z: overflow)
		bsf	_C		;set carry to show over/underflow
		return

_inbounds	bcf	_C		;clear the carry to show we stayed inbounds
		return
		Page
;--------------------------------------------------------------------------
; Main Program --
;	Initialize the registers
;	Init. the PLL and LCD
;	Loop doing the background processing
;--------------------------------------------------------------------------

Start		movlw	20			;wait .2 sec for the LCD to power up
		call	Delay_Long
		call	Init			;then initialize everything

		bsf	INTCON,GIE		;and then enable global interrupts

		call	Init_LCD		;and initialize the LCD display
						;(ints must be enabled to get
						;chars to the LCD display)
		call	LCD_Freq		;display the frequency
		call	LCD_BitRate		;and bit rate

background	btfss	LCDFreqUpdRqst		;see if the LCD freq needs updating
		goto	DisplayStatus		;no, move on
		call	LCD_Freq		;do it, if so
		bcf	LCDFreqUpdRqst		;and note that we did it

; Now see if any of the sync/lock status bits changed, and if so
; update the LCD display

DisplayStatus
		bcf	CurrSync,CarrierDetFlag	;first, get the carrier and synth
		btfss	CarrierDet		;from the hardware and put in
		bsf	CurrSync,CarrierDetFlag	;CurrSync.  Signals are low true

		bcf	CurrSync,SynthFlag
		btfss	SynthLock
		bsf	CurrSync,SynthFlag

		;now, see if any Sync bits changed since last LCD update
		movfw	CurrSync
		xorwf	OldSync,w		;non zero means something changed
		btfsc	_Z
		goto	DispStaID		;nothing changed - go do the Station ID

		;something changed, so update the sync status display
		movfw	CurrSync		;temporarily, use OldSync to display
		movwf	OldSync			;current status
		movlw	DispRow2+StatPos	;go to status position on LCD
		call	LCD_cmd
		movlw	7			;there are seven status chars
		movwf	tempbg			;temporary counter
_stat_loop	rrf	OldSync,f
		movlw	GoodChar
		btfss	_C			;display each char
		movlw	BadChar
		call	LCD_Out
		decfsz	tempbg,f
		goto	_stat_loop
		movfw	CurrSync		;remember the display status
		movwf	OldSync

	;see if StaID changed -- if so, display
DispStaID	bcf	INTCON,GIE	;turn off interrupts to get a clean
		movfw	StaID_MSB	;read of the two bytes
		movwf	RegB
		movfw	StaID_LSB
		movwf	RegC
		bsf	INTCON,GIE	;and turn interrupts back on

		movfw	RegB		;see if it changed
		xorwf	OldStaID_MSB,w
		btfss	_Z
		goto	DispSta		;MSB changed - go display
		movfw	RegC		;MSB same -- check LSB
		xorwf	OldStaID_LSB,w
		btfss	_Z
DispSta		call	LCD_StaID	;changed so display the value

DispBR		movfw	OldBitRate	;see if bit rate changed
		xorwf	BitRate,w
		btfss	_Z		
		call	LCD_BitRate	;yup - display the new value

		goto	background	;and just keep going around
		
		PAGE
;--------------------------------------------------------------------------
; Init -- Initialize I/O pins and variables, and interrupts
;--------------------------------------------------------------------------
Init		
		BANKSEL	TRISB		;set up direction of i/o pins
		clrf	TRISB		;TRISB all inputs, normally
		comf	TRISB,f
		clrf	TRISA		;and TRISA all outputs
		bsf	Select4800_Bit	;except this bit for now is an input
		BANKSEL	PORTA
		movlw	K9600
		btfsc	Select4800	;check jumper: skip if 9600 selected
		movlw	K4800
		movwf	Bit_K
		BANKSEL	TRISA
		clrf	TRISA		;put TRISA back to all outputs now
		BANKSEL	PORTB		;switch back to bank zero

	;Init the serial output and Synth clock
		SC104_MARK
		Synth_Clk_Low

	;Init the PLL Control Register
		Synth_Enb_High
		movlw	10		;wait .1 sec to make sure Synth is up
		call	Delay_Long
		movlw	PLL_Cntl
		call	PLL_Send	;send w to the PLL
		Synth_Enb_Low		;Take ENB back low

		nop			;Wait a beat
		Synth_Enb_High		;Raise ENB again
		movlw	high RefFreq	;and load the Ref counter
		call	PLL_Send	;divide by n
		movlw	low RefFreq
		call	PLL_Send
		movlw	high AuxRefCntr
		call	PLL_Send
		movlw	low AuxRefCntr
		call	PLL_Send
		Synth_Enb_Low

	;Init the PLL Tx (LO1) and Rx (LO2) counters
		call	Get_Last_Chan	;fetch the last channel from eeprom to Chan
		call	Set_PLL		;and init the PLL counters for LO1 and LO2

	;Init the counter and accumulator for SC104 bits
		movlw	6		;six bits go into each byte
		movwf	SC104_in_cntr
		clrf	SC104_in	;clear the incoming-bit accumulator

	;Init Flags to all zeroes and reset sync status
		clrf	Flags
		call	Reset_Sync

	;Init the interrupt handler
		bcf	INTCON,RBIF	;clear the interrupt flag and
		bsf	INTCON,RBIE	;enable interrupts on PORTB change

		return

;------------------------------------------------------------------------
;Reset_Sync -- clears all sync bits, and inits all conf counters to
;	       mid-scale
;------------------------------------------------------------------------
Reset_Sync	movlw	0x80
		movwf	CarrierSync
		movwf	BitSync
		movwf	WordSync
		movwf	FrameSync
		movwf	CarrierSync
		clrf	CurrSync
		clrf	OldSync
		bsf	StaID,7		;mark StaID as invalid
		movlw	3
		movwf	BitRate		;set up to seek a new bit rate
					;starting at 50bps
		bcf	CurrSync,BitSyncFlag

		return

		PAGE
;------------------------------------------------------------------------
;PLL_Send -- sends the byte in w to the MC145162 synthesizer
;------------------------------------------------------------------------
PLL_Send	movwf	temp		;save the byte to send
		movlw	8		;set up to send 8 bits
		movwf	delay_cntr2	;use this as a temp reg - it is safe
					;when this routine is called to do so
		Synth_Clk_Low		;make sure clock is low to start
		BANKSEL	TRISB		;and make the data pin an output
		bcf	Synth_Data_Bit
		BANKSEL	PORTB

_PLL_Next	rlf	temp,f		;put next bit into carry
		Synth_Data_Low		;assume data bit is zero
		btfsc	_C		;skip if the data should be zero
		Synth_Data_High		;data=1, raise the data line
		Synth_Clk_High		;give the PLL a clock pulse
		Synth_Clk_Low
		decfsz	delay_cntr2,f	;see if done
		goto	_PLL_Next	;nope - go send next bit

		BANKSEL	TRISB		;return data pin to being an input
		bsf	Synth_Data_Bit
		BANKSEL	PORTB

		return			
;--------------------------------------------------------------------------
; Set_PLL: Adds Chan to the LO1_Base, and sends LO1 and Synth2 divide-by-n
;	   values to the PLL.  (Calls PLL_Send).  Since this is only
;	   called on init and on a freq change, also clear the sync
;	   status.  After Init, called only from the interrupt service
;	   routine, so the use of the IO pins will not collide with
;	   sending data to the LCD.
;--------------------------------------------------------------------------
Set_PLL		movlw	high LO1_Base	;get the base value
		movwf	LO1
		movlw	low LO1_Base
		movwf	LO1+1
		movfw	Chan		;add the channel offset
		addwf	LO1+1,f		;and add it to the LO1 base frequency
		btfsc	_C
		incf	LO1,f

		Synth_Enb_Low		;send out the 32 bit word
		movfw	LO1
		call	PLL_Send
		movfw	LO1+1
		call	PLL_Send
		movlw	high Synth2
		call	PLL_Send
		movlw	low Synth2
		call	PLL_Send

		Synth_Enb_High		;pulse ENB to latch data
		Synth_Enb_Low

		call	Set_Last_Chan	;save this channel in the eeprom

		call	Reset_Sync	;can't be in sync if changing freq
		movlw	3		;start bit rate hunting at 50bps
		call	SetBitRate

		return
		PAGE
;--------------------------------------------------------------------------
; SetBitRate: sets the BitRate, sets up TMR0, and turns on the
;		TMR0 interrupt
;		Enter with BitRate in W:
;			1 : 200 bps
;			2 : 100 bps
;			3 :  50 bps
;--------------------------------------------------------------------------
SetBitRate	
		bcf	INTCON,T0IE	;disable TMR0 interrupts
		movwf	BitRate		;save this bit rate
		movlw	0x80		;init BitSync conf counter
		movwf	BitSync
		bcf	CurrSync,BitSyncFlag	;declare 'out of sync'

		movfw	BitRate		;set the prescaler from BitRate

		BANKSEL	OPTION_REG	;Set OPTION register: PORTB pullups off,
		addlw	B'11000100'	;  INT:rising edge, TMR0 internal clock
		movwf	OPTION_REG	;  Prescaler to TMR0, set prescaler
		BANKSEL	TMR0
		movlw	-BitWidth	;Load the timer
		movwf	TMR0
		bcf	INTCON,T0IF	;clear any pending timer interrupt
		bsf	INTCON,T0IE	;Enable timer interrupts

		return

;--------------------------------------------------------------------------
; Get_Last_Chan: Gets the last channel number from EEPROM
;--------------------------------------------------------------------------
Get_Last_Chan
		movlw	low LastChan	;get the EEPROM address of the byte
		movwf	EEADR
		BANKSEL	EECON1
		bsf	EECON1, RD	;eeprom read
		BANKSEL	EEDATA
		movf	EEDATA,w	;put the byte into w
		movwf	Chan		;and save it in Chan
		return

;--------------------------------------------------------------------------
; Set_Last_Chan: Writes the current channel number to EEPROM
;
; This is called by Set_PLL, which is only called from within
; the interrupt handler, so interrupts are disabled when this is
; called
;--------------------------------------------------------------------------
Set_Last_Chan	movfw	Chan		;get the channel number to save
		movwf	EEDATA
		movlw	low LastChan	;address setup
		movwf	EEADR
		BANKSEL	EECON1		;switch banks
_EEWait		btfsc	EECON1,WR	;skip if previous write complete
		goto	_EEWait
		bsf	EECON1,WREN	;enable EEPROM write
		movlw	0x55		;prescribed writing sequence
		movwf	EECON2
		movlw	0xaa
		movwf	EECON2
		bsf	EECON1,WR	;Set WR to begin write
		BANKSEL	EEDATA		;switch back to bank 0
		return

		PAGE
;-------------------------------------------------------------------------
; Delay_100us -- generates a 100 us delay.  Uses only W
;-------------------------------------------------------------------------
Delay_100us	movlw	-23
_t_loop		addlw	1		;increment w
		btfss	_C		;skip if carry			
		goto	_t_loop
		return
;-------------------------------------------------------------------------
; Delay_10ms -- generates a 10 ms delay.  
;		Uses delay_cntr1, calls Delay_100us
;-------------------------------------------------------------------------
Delay_10ms	movlw	100
		movwf	delay_cntr1
_loop10ms	call	Delay_100us
		decfsz	delay_cntr1,f
		goto	_loop10ms
		return
;-------------------------------------------------------------------------
; Delay_Long -- generates a delay of N * 10ms, where N is in W on entry
;		(Uses delay_cntr2, calls Delay_10ms)
;-------------------------------------------------------------------------
Delay_Long	movwf	delay_cntr2	;save the count
_looplong	call	Delay_10ms	;call Delay_10ms that number of
		decfsz	delay_cntr2,f	;times
		goto	_looplong
		return

		PAGE
;--------------------------------------
;bin2bcd - 10 bit binary to bcd conversion
;
; Enter with ten bit number in RegB:RegC
;   (ie, 2 msb's in lsbs of RegB, 8 lsb's in RegC)
;   Make sure six msbs of RegB are zero
; Routine converts numbers in the range of 0-999
; to ascii and send the three digits to the LCD
;---------------------------------------
bin2bcd
		movlw	B'00000001'	;conversion code found on the
		movwf	RegA		;Internet -- it is magic, and I
					;have no idea how it works.  If the
b2bloop		rlf	RegC,f		;input is 1000-1023, the code generates
		rlf	RegB,f		;0xA for the hunds digit, so if Station
		rlf	RegA,f		;id values over 999 are ever used, then
					;this is the code which would have to
		btfsc	_C		;be modified to detect an 'A' in the 
		goto	b2bascii	;hunds digit and convert that to 
					;two digits of '1' and '0'.
		movlw	0x03
		addwf	RegB,f
		btfss	RegB,3
		subwf	RegB,f

		movlw	0x30
		addwf	RegB,f
		btfss	RegB,7
		subwf	RegB,f

		goto	b2bloop

b2bascii
		movfw	RegA		;Hunds value
		call	LCD_ascii

		swapf	RegB,w		;Tens value
		call	LCD_ascii

		movfw	RegB		;Ones value
		call	LCD_ascii
		return

		PAGE
;-----------------------------------------------------------------------
; LCD_cmd --  Requests that a control char be sent to the LCD display
; LCD_Out --  Requests that a char be sent to the LCD display
; LCD_ascii - Converts a digit 0-9 to ascii and sends it to the LCD
;	      Char sends happen on the next TMR0 interrupt
;
;		*****This may be called ONLY from the background********
;		*****    not from within the interrupt handler  ********
;-----------------------------------------------------------------------

LCD_cmd		bcf	RS		;note this is a command
		goto	LCD_Out2

LCD_ascii	andlw	0x0F
		addlw	"0"

LCD_Out		bsf	RS		;note this a char to send
LCD_Out2	movwf	LCD_char	;save the byte to send
		bsf	LCDFlag		;signal the interrupt handler we have something
					;for sending - it will get sent on
					;the next TMR0 interrupt

_LCD_Out_Loop	
		btfsc	LCDFlag		;wait until it has been sent
		goto	_LCD_Out_Loop

		return

;--------------------------------------------------------------------------
; Init_LCD -- initialize the LCD display
;--------------------------------------------------------------------------
Init_LCD
		movlw	0x38		;8 bit mode, 1/16th duty cycle,
		call	LCD_cmd		;5x8 font
		call	Delay_10ms
		movlw	0x38
		call	LCD_cmd
		movlw	0x38
		call	LCD_cmd
		call	Delay_10ms
		movlw	0x38
		call	LCD_cmd
		movlw	0x06		;chars add to the right
		call	LCD_cmd
		movlw	0x0C		;Disp on, no cursor, no blink
		call	LCD_cmd
		movlw	0x01		;Clear display, home cursor
		call	LCD_cmd
		call	Delay_10ms


	;LCD is now up and running, and we can init the displayed data		

		movlw	DispRow1+FreqPos+FreqWidth
		call	LCD_cmd
		movlw	"k"		;put up the 'k' for 'khz'
		call	LCD_Out
		movlw	DispRow1+IDLabelPos
		call	LCD_cmd
		movlw	"I"		;put up "ID"
		call	LCD_Out
		movlw	"D"
		call	LCD_Out
		movlw	DispRow1+StatPos
		call	LCD_cmd
		movlw	"S"		;put up "SICBWFP" for status indicators
		call	LCD_Out
		movlw	"I"
		call	LCD_Out
		movlw	"C"
		call	LCD_Out
		movlw	"B"
		call	LCD_Out
		movlw	"W"
		call	LCD_Out
		movlw	"F"
		call	LCD_Out
		movlw	"P"
		call	LCD_Out

		movlw	DispRow2+BitRatePos+BitRateWidth
		call	LCD_cmd		;put up '0b' for bits/sec in second row
		movlw	"0"
		call	LCD_Out
		movlw	"b"
		call	LCD_Out

		;put dashes in the station ID position, since
		;we don't know what it is

		call	IDDash

		return

;----------------------------------------------------
; IDDash -- pusts dashes in the Station ID Display
;----------------------------------------------------
IDDash		movlw	DispRow2+IDPos
		call	LCD_cmd
		movlw	IDWidth
		movwf	tempbg
		movlw	"-"
_IDDashLoop	call	LCD_Out
		decfsz	tempbg,f
		goto	_IDDashLoop
		movlw	" "
		call	LCD_Out
		bsf	OldStaID,7		;note that it has dashes
		bsf	StaID,7
		return

;----------------------------------------------
;LCD_Freq -- Converts and displays the 3-digit frequency
;----------------------------------------------
LCD_Freq	movlw	high 285
		movwf	RegB
		movfw	Chan		;convert chan to freq
		addlw	low 285		;by adding 285 khz
		movwf	RegC
		btfsc	_C
		incf	RegB,f
		bcf	LCDFreqUpdRqst	;clear the request

		movlw	DispRow1+FreqPos	;go to Freq position
		call	LCD_cmd

		call	bin2bcd		;convert it to bcd-ascii
					;and display

		return
;---------------------------------------------------------
;LCD_StaID -- converts and displays the Station ID
;---------------------------------------------------------
LCD_StaID	movfw	RegB
		movwf	OldStaID_MSB	;remember that we displayed this ID
		movfw	RegC
		movwf	OldStaID_LSB

		btfss	RegB,7		;see if valid
		goto	Valid_ID
		call	IDDash		;nope, display dashes
		return

Valid_ID	movlw	DispRow2+IDPos
		call	LCD_cmd
		call	bin2bcd		;display it
		return
;---------------------------------------------------------
;LCD_BitRate -- displays the current bit rate
;---------------------------------------------------------
LCD_BitRate	movfw	BitRate		;remember that we have displayed
		movwf	OldBitRate	;the current bit rate
		movlw	DispRow2+BitRatePos
		call	LCD_cmd		;and then actually display it
		movfw	OldBitRate
		call	BR_First
		call	LCD_Out
		movfw	OldBitRate
		call	BR_Second
		call	LCD_Out
		return
		END
