;
; clock.asm -- a sunrise/sunset based controller for the outside lights.
;

;
; source code for gpasm by Byron Jeff - May 20th, 2001
;

; This controller drives an output (PORTB:1) based on the time. This output
; is used to drive transistor driven relays which switch on/off the outside
; house lights. Time is tracked using a 32khz crystal connected to Timer1.
; Sunrise and Sunset is computed using linear interpolation and a small
; sunrise/sunset table with one entry per month. The controller drives a LED
; display which shows the current time. The time is also output out the 
; wloader serial port every timer1 interrupt, which is every 16 seconds.
; Finally a cheap wall clock module is used to maintain precise time by 
; resetting the minute value of the controller once an hour. 

; LICENSE: This software can be used, modified, and redistributed to personal
; non-commercial use only. Please contact the author at byron@cc.gatech.edu
; for any commercial usage of this software.      
;
; define PIC device type
;
;	device	pic16F877

;
; define config fuses
;
;	__config	CP=off,WDT=off,PWRT=off,OSC=hs
	radix	dec	; Default numbers in decimal. I use 0x for hex.

;
; include PIC register definitions, and some macros
; 
	include "picreg.h"

; Define identifiers for strings
	cblock	0
	HELLO
	PTICKMSG
	CRLF
	PFAIL
	endc

; Define bits for LED control in PORTB
	cblock	0
	LEDCLOCK
	LEDDATA
	LCDE
	endc

; Define some variables
	cblock	0x2c
	tempw	
	dtempw	
	bcdhi
	bcdlo
	nine
	vportb
	vportd
	txout
	c1
	c2
	pscnt
	psoff
	seconds
	month
	day
	hours
	minutes
	minsync		; Sync minutes from this number every hour
	syncdelay	; number of minutes to delay after hourly sync
	count
	target
	column
	nexttmr1
	lights		; Current status of the lights.
	sunoff		; Sunrise/Sunset offset
	sunnext		; next month's sunrise sunset. Also fraction
	sunsign		; Sign of offset during month.
	sunfract	; Fractional sum off the interpolated offset
	sunmin		; sunrise/sunset minute for interpolation
	sunhour		; sunrise/sunset hour for interpolation
	srmin		; Sunrise time minute for the day
	srhour		; Sunrise time hour for the day
	ssmin		; Sunset time minute for the day
	sshour		; Sunset time minute for the day
	switch		; Current value of the switch. 1 when switch changes
	lastswitch	; last read value of the switch
	switchdelay	; Numer of ticks to delay before checking switch
	state		; Current input state. 0 is normal operation.
	rangeval	; Converted range value of the A/D converter
	adtarget
	admapcnt
	lastadc		; Keeps the last adc value
	nextadc		; Keeps the next adc value
	display		; NOTE: This must be the last variable defined!
	endc	

;
; code start
;
	org	0
	goto	init

	org	4		; Interrupt vector
	bcf	INTCON,T0IF	; Clear the interrupt
	bsf	TMR1H,7
	retfie			; Leave.

	; printstr will print the string marked by value in W
printstr
	movwf	psoff		; Save the string offset
	rlf	psoff,F		; Double so that jumptable works.
	clrf	pscnt		; Char count
psloop1 movf	psoff,W		; Set the offset
	addwf	PCL,F		; Do the jump
	call	hellostr	; Do the hello string
	goto	pscont1		; Continue
	call	tout		; Do the address string
	goto	pscont1		; Continue
	call	crlfstr		; Do the crlf string
	goto	pscont1		; Continue
	call	powerfail	; Do the powerfail string
	goto	pscont1		; Continue
pscont1	iorlw	0		; See if we're at the end of the string
	btfsc	STATUS,Z	; Check the Z flag
	return			; We're done if it's set.
	call	dbgout		; send the char
	incf	pscnt,F		; and set up for next char
	goto	psloop1		
	
hex2ascii
	andlw	15		; Limit to table
	addwf	PCL,F		; Jumptable
	dt	"0123456789ABCDEF"	

hellostr	
	movf	pscnt,W		; Get the char offset of the string
	addwf	PCL,F		; Jump table
	dt	"Hello to serial testing\n\r",0

tout	movf	pscnt,W		; Get the char offset of the string
	addwf	PCL,F		; Jump table
	dt	"Powertick: ",0

powerfail
	movf	pscnt,W		; Get the char offset of the string
	addwf	PCL,F		; Jump table
	dt	"Powerfail!\n\r",0

crlfstr	movf	pscnt,W		; Get the char offset of the string
	addwf	PCL,F		; Jump table
	dt	"\n\r",0

getlastday
	decf	month,W		; Get the last day of the month + 1
	addwf	PCL,F		; 
	dt	32,29,32,31,32,31,32,32,31,32,31,32

getchar	andlw	15		; Only the low nybble
	addwf	PCL,F		; Jump table.

	dt	0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07	
	dt	0x7f,0x67,0x77,0x7c,0x39,0x5e,0x79,0x71	

highbcd	andlw	0x0f		; Mask off low part of nybble (already swapped)
	addwf	PCL,F		; Jump table
	dt	0x00,0x16,0x32,0x48,0x64,0x80,0x96

; Note that for these two routines that W contains the month offset.
; In this way it can get the sunrise/sunset value for the current month or
; the next month, which is required to compute the difference over the span
; of the month. Note that January is repeated at the end of the list so that
; The december values can be computed without modification to the routines.

sunrise	addwf	month,W
	addlw	255		; Subtract 1 to offset for month from 1-12
	addwf	PCL,F
	dt 	163,154,126,85,49,28,31,50,72,92,117,144,163

sunset	addwf	month,W
	addlw	255		; Subtract 1 to offset for month from 1-12
	addwf	PCL,F
	dt 	40,69,94,118,141,163,172,158,123,82,45,29,40

admapoffset	
	movf	state,W		; Get the value of state
	addwf	PCL,F
	dt	2,19,7,9,3	; Maximum values for each state.

maxval	movf	state,W		; Get the value of state
	addwf	PCL,F
	dt	99,12,31,23,59	; Maximum values for each state.

hex2bcd	movwf	tempw		; Save it in temp register
	swapf	tempw,W
	call	highbcd		; Get the BCD value of the high nybble
	movwf	bcdlo		; Save for later
	andlw	0xf0		; Mask off the low nybble for later add.
	movwf	bcdhi
	movlw	0x0f		; Mask off the high nybble of bcdlo
	andwf	bcdlo,F
	movlw	9		; Put a nine to subtract in the register
	movwf	nine
; Add the low nybbles together. Compensate if the sum is greater than 9
; Then add the result to the high nybble of the BCD of the high nybble.
; The low digit add plus the compensation may have pushed the low digit back
; into the hex range. Need to check and recompensate. For example 1F maps to
; (16 + 0F). The low digit sum is 15 hex and with the compensation is bumped
; to 1B. It needs to be compensated again by adding 6 more to get to 21 BCD.
	movf	tempw,W		; Get the original number
	andlw	0x0f		; Mask off high part of the nybble
	addwf	bcdlo,W		; Add the low nybble of the high digit value.
	subwf	nine,F		; See if bigger than nine
	btfss	STATUS,C	; Less than 9
	addlw	6		; Add 6 to compensate for hex digits from add.

; This is where recompensate the low digit sum if necessary. Note that 
; This one only checks the low digit of the sum since presumably there will
; be some high digit carry when adding 8+9+6 for example.
	movwf	bcdlo
	movlw	9		; Put a nine to subtract in the register
	movwf	nine
	movlw	0xf		; Mask for low nybble of low digit sum
	andwf	bcdlo,W
	subwf	nine,F		; See if bigger than nine
	clrw
	btfss	STATUS,C	; Less than 9
	movlw	6		; Add 6 to compensate for hex digits from add.
	addwf	bcdlo,W		; Add the low digit sum with any more comp...
	addwf	bcdhi,W		; Add the high part of BCD for final result.
	return

init
	bcf	STATUS,RP0			;register page 0
	movlw	0		; init PORTE0 to input
	movwf	PORTE		;initialize port E so serial is out and E0
	movlw	0xff
	movwf	PORTD		;initialize port D so that LEDs are on
	movlw	2
	movwf	PORTB
	movwf	lights		; Initialize the lights on
	movlw	5
	movwf	T2CON
	clrf	column		; start display in column 0
	clrf	TMR1L		; Clear timer 1 because reset doesn't
	clrf	TMR1H
	bcf	PIR1,TMR1IF	; Reset the timer1 flag			

	movlw	0x3f		; External Async 1:8 on setting for Timer1
	movwf	T1CON		; Turn Timer1 on with 16 second timeout
	clrf	seconds
	movlw	0
	movwf	minutes
	movwf	minsync
	clrf	syncdelay
	movlw	7		; Always set time to standard time. 
	movwf	hours		; Clock does not reset to daylight time.
	movlw	8
	movwf	day
	movlw	9
	movwf	month

	MOVLW   0x81             ; F/32 Clock, A/D is on, Channel 0 is selected
	MOVWF   ADCON0           ; 

	BCF     PIR1,   ADIF     ; Clear A/D interrupt flag bit 
	BCF     INTCON, PEIE     ; Disable peripheral interrupts FOR NOW!
	BCF     INTCON, GIE      ; disable all interrupts FOR NOW!

	bsf	STATUS,RP0	;register page 1
	movlw	4		; AD0,AD1,AD3 analog. Other digital
	movwf   ADCON1           ; Configure A/D inputs 
	BCF     PIE1,ADIE     	; DISABLE A/D interrupts 
	movlw	0xff		; Make PORTA all inputs
	movwf	PORTA		; register TRISA in page 1.
	movlw	0x05		; RB2 is power detect input. RB0 is ext clock
	movwf	PORTB		; register TRISB
	movlw	0x00		; Whole port as an output.
	movwf	PORTD		; register TRISD
	movwf	PORTE		; Port TRISE all outputs
	movlw	65
	movwf	T2CON		; PR2 set to 65 ticks for 52 uS delay
	bcf	STATUS,RP0	;register page 0
	clrf	PCLATH

	movf	PORTA,W		; Get the current value of the switch
	andlw	4		; Mask
	movwf	lastswitch	; And store it
	clrf	switch		; Switch has not been pressed.
	clrf	switchdelay	; No delay. Can check now.
	clrf	state		; Clear state. Normal mode until switch hit.

	movlw	HELLO
	call	printstr

	call	setrise		; Set the sunrise time for today
	call	setset		; Set the sunset time for today
	
	call	updisp		; Initialize the display. 
				; Will come back to settick

settick
	movf	TMR1L,W
	addlw	4
	movwf	nexttmr1

main	
	btfsc	PIR1,TMR1IF	; Wait for timer 1 timeout
	goto	tmr1tick

dodisplay
	movf	TMR1L,W		; Get current timer 1 value
	xorwf	nexttmr1,W	; See if a tick has elapsed.
	btfss	STATUS,Z	; Yup. Update the display
	goto	main		; Nope. Wait until then.

; Do the A/D converter work. We'll do continuous scans. lastadc will always
; have the current value...
	
	btfsc	ADCON0, GO	; Restart conversion if it's done.
	goto	moveadc
	call	idelay		; Be sure that 52uS elapses.
        BSF     ADCON0, GO       ; Start A/D Conversion 

moveadc	movf	ADRESH,W	; Get the A/D value
	call	admap
	movwf	nextadc		; And save.
	xorwf	lastadc,W	; See if lastadc and day match
	btfsc	STATUS,Z	; More to do if they do not match
	goto	checkswitch	; Continue to checkswitch if the same
; Here we transfer the adc value if the appropriate state is set. The
; date/time variables are arranged so that they can be indexed.
	movf	state,W		; Get the state. Done if 0
	btfsc	STATUS,Z
	goto	checkswitch	
	addlw	month-1		; Compute the address
	movwf	FSR		; And stick in FSR for indexing.
	movf	nextadc,W	; Get the value
	movwf	lastadc		; Now this the the current ADC value.
	movwf	INDF		; Transfer to the day
	goto	printdate	; And update the display

; Do the switch work. lastswitch has the last read value of the switch. We
; advance the state each time the switch is pressed. To do debouncing we put
; in a tick delay on the switch so that it cannot be checked again until the
; switch delay has elapsed. 

checkswitch
	movf	switchdelay,W
	btfss	STATUS,Z	; It's zero. We can check the switch
	goto	switchdec	; Decrement the delay
	
	movf	PORTA,W		; Get the current value of the switch
	andlw	4		; Mask off the switch bit only.
	xorwf	lastswitch,W	; See if the switch has changed.
	btfsc	STATUS,Z	; Changed. Got work to do.
	goto	newcol		; No change. We're done.

	movlw	4		; Toggle the lastswitch value
	xorwf	lastswitch,F

	bsf	switchdelay,6	; Set switchdelay to 64 counts.
	incf	state,F		; Update the state.
	movlw	5		; See if state rollover

	xorwf	state,W
	btfsc	STATUS,Z	; Nope. Keep the current state
	clrf	state		; Reset state to 0

	goto	printdate	; Update the display right now.

switchdec
	decf	switchdelay,F	; One less tick on the switchdelay
newcol
	movlw	16		; Add 16 to the column
	addwf	column,F
	movlw	0xa0
	xorwf	column,W	; See if past last column
	btfss	STATUS,Z	; Yes. need to reset and call updisp
	goto	newcolcont
	clrf	column		; Back to column 0.
	call	updisp		; Make sure to update the display
newcolcont
	swapf	column,W	; Get the digit to display. Remember it's in
	addlw	display		; The high nybble.
	movwf	FSR
	movf	INDF,W
	movwf	PORTD
	movf	column,W
	iorwf	lights,W	; Mask in the lights value.
	movwf	PORTB
	goto	settick

tmr1tick
	movlw	16		; Add 16 seconds
	addwf	seconds,F		
	movlw	60		; See if we exceeded max seconds
	subwf	seconds,W	; Don't change unless necessary
	btfsc	STATUS,Z	; Update if exactly 60
	goto	upsecs
	btfss	STATUS,C	; update if carry is set (positive)
	goto	printdate	; Done if no update
upsecs	movwf	seconds		; Move updated value back
	incf	minutes,F		; Move to next minute
;
; See if we need to resync the minutes with the external clock. One minute
; resolution is more than good enough. If the minute hand of the clock is
; under the sensor. Then reset the minutes to the initial minute setting
; and set the syncdelay to 5 minutes. Before all of this check the syncdelay
; and if it's not zero, decrement and skip the rest giving the minute hand
; time to clear the sensor
;
	clrw			; Checking to see if syncdelay is on
	xorwf	syncdelay,W
	btfsc	STATUS,Z	; Zero. We can check the clock
	goto	checkhour
	decf	syncdelay,F	; One less minute on the delay
	goto	upmins
checkhour			; Check the hour hand on the external clock
	btfsc	PORTB,0		; Work to do if bit is clear. hand over sensor.
	goto	upmins		; Otherwise simply go on and update the minutes
	movlw	5		; 5 minute delay before checking again
	movwf	syncdelay
	movf	minsync,W	; Get the value to resync the minute to
	movwf	minutes		; And resync the minutes. we're done.
upmins
	movlw	60		; See if we're at 60
	xorwf	minutes,W
	btfss	STATUS,Z	; Done if not 60 minutes.
	goto	checktime	; Check to see if the lights need to be on/off
	clrf	minutes		; Back to 0 minutes
	
	incf	hours,F		; Next hour
	movlw	24		; See if 24th hour
	xorwf	hours,W
	btfss	STATUS,Z	; Done if not 24 hours
	goto	checktime	; Check to see if the lights need to be on/off
	clrf	hours		; Back to 0 hours
checktime			; See if lights need to go on or off. Check
				; once a minute. But only after the minute and
				; the hour have been updated.
	movf	hours,W		; Simple test first. See where the hour is
	subwf	srhour,W	; See if we're before the sunrise hour
	btfsc	STATUS,Z	; More to do if hour == srhour
	goto	chksrmin
	btfsc	STATUS,C	; Lights on if hour < srhour
	goto	lightson	; Which is true if carry is set.

	movf	hours,W		; Simple test next. See where the hour is
	subwf	sshour,W	; See if we're after the sunset hour
	btfsc	STATUS,Z	; More to do if hour == sshour
	goto	chkssmin
	btfss	STATUS,C	; Lights on if hour > sshour
	goto	lightson	; Which is true if carry is clear.
	goto	lightsoff	; Lights off if between sunrise and sunset

chksrmin			; Check minutes on sunrise because hours are eq
	movf	minutes,W	; Simple test. See where the minutes are
	subwf	srmin,W		; See if we're before the sunrise minute
	btfsc	STATUS,C	; Lights on if min < srmin
	goto	lightson	; Which is true if carry is set.
	goto	lightsoff	; Otherwise turn the lights off

chkssmin			; Check minutes on sunset because hours are eq
	movf	minutes,W	; Simple test. See where the minutes are
	subwf	ssmin,W		; See if we're after the sunset minute
	btfss	STATUS,C	; Lights on if min > ssmin
	goto	lightson	; Which is true if carry is clear.
	goto	lightsoff	; Otherwise turn the lights off

lightson
	bsf	lights,1	; Turn the lights on
	goto	doday
lightsoff
	bcf	lights,1	; Then the lights off
doday
	movf	minutes,F		; See if minutes is 0
	btfss	STATUS,Z	; Continue only if 0
	goto	printdate	; All updates done
	movf	hours,F		; See if hours are 0
	btfss	STATUS,Z	; Continue only if 12 midnight.
	goto	printdate

	incf	day,F		; Next day
	call	getlastday	; Get the last day of the month
	xorwf	day,W
	btfss	STATUS,Z	; Done if not last day
	goto	newday		; Set the sunrise/sunset time
	movlw	1		; day starts at day 1.
	movwf	day
	incf	month,F		; Next month
	movlw	13		; See if last month of the year
	xorwf	month,W
	btfss	STATUS,Z	; Done if not last month
	goto	newday		; Set the sunrise/sunset time
	movlw	1		; month starts at 1.
	movwf	month

newday
	call	setrise		; Set the sunrise time for the new day
	call	setset		; Set the sunset time for the new day

printdate			; Print the current date
	movf	month,W
	call	printbyte
	movlw	'/'
	call	dbgout
	movf	day,W
	call	printbyte
	movlw	' '
	call	dbgout
	movf	hours,W
	call	printbyte
	movlw	':'
	call	dbgout
	movf	minutes,W
	call	printbyte
	movlw	':'
	call	dbgout
	movf	seconds,W
	call	printbyte
	movlw	':'
	call	dbgout
	movlw	CRLF
	call	printstr
	bcf	PIR1,TMR1IF	; Reset the timer1 flag			

; This is the fallthrough to updating the display. Since updisp is now
; a callable function we need to make the call then go to settick
	call	updisp
	goto	settick		; loop forever. Reset the timer tick too.

; Need to figure out blinking here. We use some bit from out of timer 1 to
; set the blink rate. If the state matches a particular value to be displayed
; We have a new routine dispblank that will blank a particular slot. What we
; need to do here is first see if we're blanking at all. We can do this
; cheaply by temporarily setting the top bit of the state when we don't want 
; to blank. REMEMBER TO CLEAR AT THE END OF THE ROUTINE! Once done for each
; of the fields check to see if the state matches the state for that field.
; If it does then we blank the field. Otherwise we display. The state will
; not match any fields when the state is 0 (normal mode) or when the top bit
; is set. The effect should be that the display should be steady when the
; state is 0, and the particular field attached to the current state should
; blink when the state is not 0. The blink rate is determined by the bit
; That's checked in the timer 1 register. A quarter to half second should
; be good.

	
updisp	movlw	display		; Use FSR to point to display
	movwf	FSR

	btfsc	TMR1H,3		; Transfer the timer 1 bit to the top bit of
				; of the state.
	bsf	state,7

	movlw	1		; See if the state is 1: month
	xorwf	state,W		
	btfss	STATUS,Z	; Blank if state matches
	goto	showmonth	; Display if no match
	call	dispblank	; Blank the field if it matches
	goto	dispday
	
showmonth
	movf	month,W		; Month first
	call	dispbyte
dispday
	movlw	2		; See if the state is 2: day
	xorwf	state,W		
	btfss	STATUS,Z	; Blank if state matches
	goto	showday		; Display if no match
	call	dispblank	; Blank the field if it matches
	goto	dispdash
	
showday
	movf	day,W
	call	dispbyte
dispdash
	movlw	0x40		; A dash between the date and time
	movwf	INDF
	incf	FSR,F		; Now skip that spot
	movlw	3		; See if the state is 3: hours
	xorwf	state,W		
	btfss	STATUS,Z	; Blank if state matches
	goto	showhours	; Display if no match
	call	dispblank	; Blank the field if it matches
	goto	dispminutes
	
showhours
	movf	hours,W
	call	dispbyte
dispminutes
	movlw	4		; See if the state is 4: minutes
	xorwf	state,W		
	btfss	STATUS,Z	; Blank if state matches
	goto	showminutes	; Display if no match
	call	dispblank	; Blank the field if it matches
	goto	dispend
	
showminutes
	movf	minutes,W
	call	dispbyte
dispend
	bcf	state,7
	return

; Set the sunrise/sunset time. We do this by interpolating the sunrise/sunset
; times at the beginning of the month. Linear interpolation over a 32 day 
; month is done because it makes the fixed binary arithmatic easier. The basic
; algorithm
; 1) Get the current month's first day time
; 2) Get the difference from next month's time. Divide by 32.
; 3) Add 1/32 for each day into the month. Truncate to an integer minute.
; 4) Add/subtract the extra minutes from the 1st day offset
; 5) Adjust the offset to the correct minute/hour time for that day.

; We only need to do this once a day at midnight.

setrise	movlw	1
	call	sunrise		; Get the sunrise offset for the next month
	movwf	sunnext
	movlw	0		; Get the sunrise offset for this month
	call	sunrise		
	movwf	sunoff
	movlw	5		; Start of the offset time 5:00 AM
	movwf	sunhour	
	call	interp
	movf	sunhour,W
	movwf	srhour
	movf	sunmin,W
	movwf	srmin
	return

setset	movlw	1
	call	sunset		; Get the sunset offset for the next month
	movwf	sunnext
	movlw	0		; Get the sunset offset for this month
	call	sunset		
	movwf	sunoff
	movlw	17		; Start of the offset time 5:00 PM
	movwf	sunhour	
	call	interp
	movf	sunhour,W
	movwf	sshour
	movf	sunmin,W
	movwf	ssmin
	return

interp				; Interpolate the sunrise/sunset time
				; input in sunoff/sunnext/sunhour
				; Result in sunhour/sunmin
	movf	sunoff,W	; Get the offset for the current month
	subwf	sunnext,W	; Find the difference between the two offsets
	movwf	sunsign		; Keep the sign
	btfsc	sunsign,7	; Check the sign bit
	sublw	0		; Negate by subtracting W from 0.
	movwf	sunnext		; Negate if negative. Sign still in sunsign

; We treat sunnext as a III.FFFFF fixed number. We need to multiply this
; offset by the number of days since the beginning of the month. However
; If the sum exceeds 1 we need to take the integer part and add/subtract it
; from the original offset and then remove it from our running sum
	clrf	sunfract		; Start at zero
	movf	day,W		; Get the current day
	addlw	255		; Subtract one. This is our count
	btfsc	STATUS,Z	; Done if first day of the month.
	goto	sscont2
	movwf	c1		; Put in counter
ssloop	movf	sunnext,W	; Get the fraction
	addwf	sunfract,W	; Add to the current fraction
	movwf	sunfract
	movwf	tempw		; Save so we can shift and swap
	rrf	tempw,F		; Shift
	swapf	tempw,W		; Swap. III now in low three bits of W.
	andlw	7		; Ditch the rest.
	btfsc	sunsign,7	; See if we need to subtract
	sublw	0		; Negate W if we need to subtract.
	addwf	sunoff,F		; Add to the offset
	movlw	0x1f		; Only keep the fractional part
	andwf	sunfract,F	; So mask off the integer part in sunfract
	decfsz	c1,F		; Loop
	goto	ssloop
sscont2
	movf	sunoff,W	; Get the offset in minutes
	movwf	sunmin		; And store;
; Note that sunmin can be more than an hour after base hour. So we have to 
; subtract 60 and add an hour until the result is less than 60
ssloop2	movlw	60		; The magic number. 1 hour
	subwf	sunmin,W	; Take 60 minutes off
	btfss	STATUS,C	; Result is positive. More to do.
	goto	sscont1	
	incf	sunhour,F		; Add one hour
	movwf	sunmin		; Put the results in minutes
	goto	ssloop2		; and do it again.
sscont1				; Done. Correct hours and minutes.
	return

dispblank			; Clear the space for blinking.
	clrf	INDF		; Clear the space
	incf	FSR,F
	clrf	INDF		; Clear the space
	incf	FSR,F
	return

dispbyte
	call	hex2bcd
dispbyte_nohex
	movwf	dtempw
	swapf	dtempw,W
	call	getchar
	movwf	INDF
	incf	FSR,F
	movf	dtempw,W
	call	getchar
	movwf	INDF
	incf	FSR,F
	return

dbgout	movwf	txout		; Save the char to send
	movlw	8		; Number of bits to send
	movwf	c1	
	bsf	PORTE,2		; Send the start bit
	call	idelay		; Init timer 2 and wait one bit delay.
dbloop1	btfsc	txout,0		; Test the next bit to send
	goto	dbout1		; Output the 1 - remember to invert
	bsf	PORTE,2		; Output a 0. Remember to invert
	goto	dbcont1		
dbout1	bcf	PORTE,2		; Output the 1, remembering to invert
dbcont1	call	delay		; Delay one bit
	rrf	txout,F		; rotate the next bit in
	decfsz	c1,F		; Output 8 bits
	goto	dbloop1		
	bcf	PORTE,2		; Stop bit
	call	delay		; Make sure to delay stop bit time
	return			; All done.


	; Delay routines for Timer2
	; idelay resets timer 2 while delay uses current settings.
idelay	bcf	PIR1,TMR2IF	; Clear the timer 2 flag bit
	clrf	TMR2		; Clear timer2
delay	btfss	PIR1,TMR2IF	; Wait until timer2 flag comes up
	goto	delay
	bcf	PIR1,TMR2IF	; Clear it.
	return
	
	; printbyte will print the value of a single byte in W
printbyte
	movwf	psoff		; shared var with printstr.
	swapf	psoff,W		; Get high nybble
	call	hex2ascii	; Convert it
	call	dbgout		; And send it
	movf	psoff,W		; Get the low byte
	call	hex2ascii	; Convert it
	call	dbgout		; And send it too.
	return			; We're done.

	; printnum prints two consecutive bytes pointed to by W
printnum
	movwf	FSR		
	movf	INDF,W		; Get the high byte
	call	printbyte	; And print it
	incf	FSR,F		; Go to the next byte
	movf	INDF,W		; Get the low byte
	call	printbyte
	return

admap	; Maps the A/D value into smaller range
	; Input: Implied. The value of the state variable...
	; Output: mapping in W
	movwf	adtarget	; Save the target
	call	admapoffset	; Get the offset
	clrf	admapcnt	; Zero the count
; This is a divide by subtraction. We subtract the offset until the target
; goes negative
admloop	subwf	adtarget,F	; Subtract the offset
	btfss	STATUS,C	; Positive result. More to do
	goto	admcont		; Finished if negative
	incf	admapcnt,F	; 
	goto	admloop		
admcont	call	maxval
	subwf	admapcnt,W	; And subtract from the maximum count
	movf	admapcnt,W	; Get the count
	btfss	STATUS,C	; Postive subtract. More to do.
	return			; All done. Just return the count
	goto	maxval		; We can return from there...
	END

