;                      CMETERA.ASM
;        Fr. Tom McGahee's PIC CAPACITANCE METER
;
;                   Fr. Thomas McGahee
;             Don Bosco Technical High School
;                    202 Union Ave
;               Paterson, NJ  07502  USA
;
;               tom_mcgahee@sigmais.com
;                    (973)595-6655
;
;        permission granted for individual use
;
;                Microchip MPASM format
;       Specifically designed for PIC16C84. Skeleton file.
;

; note: set assembler to case-insensitive, except within strings using /c- option
;

;OPERATIONAL DESCRIPTION OF PROJECT


;the mclear (reset) switch causes a reset and return to autorange.
;the other three switches cause an immediate move to manual mode.
;zeroswitch causes most recent displayed reading to be used as 
;a zero offset. this allows the user to compare two capacitors.
;a + or - sign is displayed to indicate relative value.
;hold key down until updating stops. upon release the
;current value will be displayed.

;first line of lcd displays a + or a blinking - sign in the first
;position. This indicates whether the current displayed value is
;higher or lower than the Comparison Value (normally 0). You may
;"zero-out" any value desired by pressing the ZERO button. This will
;cause the current value to be subtracted from all future readings.
;pressing the ZERO button also forces the unit into Manual Mode.
;you can return to Auto Mode by removing the capacitor and pressing
;the reset button, or by removing the capacitor and turning the
;power off and then on again.

;manual mode can also be entered by pressing either the UP or DOWN
;buttons. (hold button down until updating stops, then release).
;UP and DOWN are used to move from one range to another in Manual Mode.
;unit starts out in Auto Mode, and will automatically switch to the
;best (most accurate) range for any given capacitor. Auto Mode has
;built-in hysteresis to prevent the capmeter from constantly
;cycling between ranges. 

;the second position on the first line is sometimes occupied by a
;blinking letter "M". This indicates that you are in Manual Mode,
;and that the Auto Mode would have issued a command to go to a
;lower range. Obviously in Manual Mode you want to be able to
;change capacitances being measured, and at such a time the value
;being measured will temporarily go to zero, causing this feature
;to kick in.

;the rest of the first line is allocated to the display of the current
;count. internally a 24 bit counter allows the accumulation of counts
;up to 16,777,216. In Auto Mode the AutoRanging kicks in at around
;524,288 to range UP, and below 32,768 it shifts DOWN. There are 4
;ranges. In the lowest range it allows a display of from .00 pf to
;5,242.88 pf. range 2 ranges from 4,096 pf to 524,288 pf. range 3
;ranges from .4096 uf to 52.4288 uf. range 4 ranges from 32.768 uf
;to 16,777.216 uf.

;in Manual Mode you can generate counts from 0 to 16,777,216
;but the accuracy outside the above specified ranges can then
;have an error greater than 1%. use autoranging for maximum
;accuracy. use Manual Mode when using the Comparison feature
;or when you don't want the unit to AutoRange. AutoRanging to zero
;always causes the unit to go to range 1 for zero. then when you
;attach a new capacitor it may take a few seconds before AutoRanging
;detects an aout-of-range condition and responds. by the way, to
;speed up from such zero excursions I have implemented the UP Range
;portion of the AutoRange so that it always goes to range 4. I do
;this because it is always quicker to down range than it is to up
;range. there is method to my madness!

;values are displayed properly positioned over the value identifiers
;such as uf nf and pf. a space is provided between each set of 3
;digits to reflect standard engineering notation. partial number
;sets are padded with _ to the right. numbers have leading zeros
;suppressed up to the decimal point. commas are added where they are
;appropriate. 

;note that sometimes many more digits are displayed than the accuracy
;warrants. in general you can trust the first 3 digits to be right
;on. the 4th digit is normally accurate +/- 1 count. additional
;digits are displayed not for additional accuracy, but simply because
;they are useful for watching variations due to temperature, etc.,
;and they are useful in matching two or more capacitors.

;basic accuracy is 1% or better within the AutoRanging values
;up to about 100 uf. by the time you get a measurement like
;16,000.000 uf the time to collect such a measurement has risen to
;almost 24 seconds. this causes the accumulated error to rise
;to a few percent.

;the first position on the second line will display a number from
;1 to 4. this represents the current range, where 1 is the lowest
;range. the middle section of the second line contains the uf
;nf and pf indicators, positioned directly below the numeric groups
;that they refer to. I chose to display more than one set of
;indicators so you can easily read something like:
;
;+     12,000.___ 
;2 fd  nf  pf A**
;
;as either .012 uf   12.000 nf    or  12,000 pf.

;after the uf/nf/pf indicators there is either an "A" or an "M"
;displayed as an indicator of either Manual or Auto Mode being
;active. this is followed by two locations that display an
;animated (moving) black box to indicate that a new count is
;in progress. this is replaced by ** to indicate that the
;on-screen count has just been updated. These asterisks will
;remain on-screen until the circuitry detects edge synchronization.
;then the flashing black boxes appear. to speed up the
;synchronization process I have separate routines for handling
;rising and falling edges.

;occassionaly you will see a message such as "OVER-RANGE!" when
;in the manual mode. if the number is still displayed, then this is
;just a cautionary warning that the displayed value lies outside
;the range where it can be guaranteed to be 1% accurate.
;if the message is the *only* thing displayed on the screen,
;then you have exceeded the 16 million count limit and really
;need to switch to a higher range.
;
;during AutoRanging a quick flash of the word "AUTORANGING" will
;appear, and you may hear a click from the internal relays. some
;ranges switch without the click sound because they are using
;external counters to switch ranges.

;a note of caution when using Manual Mode: if you have set the
;device to operate in Comparison Mode by pressing the ZERO
;button, this zero comparison value will be retained *for the
;current numbered range* even if you move to another scale.
;before moving to another scale it is therefore a good idea
;to remove the cap being measured and press the ZERO button to
;re-zero the scale. if you don't do this, then the next time
;you return to that range the Comparison value will again
;be in effect. there is nothing wrong with bridging a comparison
;over two different ranges. if you want to do that, you
;have to press the ZERO button at *each* range you want to
;perform the comparison on.

;because the ZERO button automatically causes entry into the
;Manual Mode, you usually can't run Comparison Mode in 
;Auto Mode. however, *if* you hit the reset button and have 
;a capacitor attached, the reset auto-zero feature will
;cause the current cap value to be subtracted on *every* range.
;this works with values up to about 1 uf with no problem.
;larger values will cause overflow errors on the lower ranges
;and may therefore give erroneous readings. also note that the
;time required to perfom the auto-zero function increases
;with increasing external capacitance. if the cap value is more than
;a few uf then the auto-zero logic may cause the circuitry to
;cycle endlessly in an attempt to zero the lower ranges.
;if that happens, remove the capacitor and perform a reset.





;
; directives
;


;
; note: written in all lower case so case sensitivity doesn't matter.
; however: set assembler to case-insensitive, except within strings using /c- option
;

;
; directives
;



		list		p=pic16f84	;this directive must come first

; instead of using the [ include <16f84.inc> ] we have placed the contents of the
; microchip supplied include file below for documentation purposes.
;
; P16F84.INC  Standard Header File, Version 2.00    Microchip Technology, Inc.

; This header file defines configurations, registers, and other useful bits of
; information for the PIC16F84 microcontroller.  These names are taken to match 
; the data sheets as closely as possible.  

; Note that the processor must be selected before this file is 
; included.  The processor may be selected the following ways:

;       1. Command line switch:
;               C:\ MPASM MYFILE.ASM /PIC16F84
;       2. LIST directive in the source file
;               LIST   P=PIC16F84
;       3. Processor Type entry in the MPASM full-screen interface
;==========================================================================
;
;       Verify Processor
;
;==========================================================================

        IFNDEF __16F84
           MESSG "Processor-header file mismatch.  Verify selected processor."
        ENDIF

;==========================================================================
;
;       Register Definitions
;
;==========================================================================

W                            EQU     H'0000'
F                            EQU     H'0001'

;----- Register Files------------------------------------------------------

INDF                         EQU     H'0000'
TMR0                         EQU     H'0001'
PCL                          EQU     H'0002'
STATUS                       EQU     H'0003'
FSR                          EQU     H'0004'
PORTA                        EQU     H'0005'
PORTB                        EQU     H'0006'
EEDATA                       EQU     H'0008'
EEADR                        EQU     H'0009'
PCLATH                       EQU     H'000A'
INTCON                       EQU     H'000B'

OPTION_REG                   EQU     H'0081'
TRISA                        EQU     H'0085'
TRISB                        EQU     H'0086'
EECON1                       EQU     H'0088'
EECON2                       EQU     H'0089'

;----- STATUS Bits --------------------------------------------------------

IRP                          EQU     H'0007'
RP1                          EQU     H'0006'
RP0                          EQU     H'0005'
NOT_TO                       EQU     H'0004'
NOT_PD                       EQU     H'0003'
Z                            EQU     H'0002'
DC                           EQU     H'0001'
C                            EQU     H'0000'

;----- INTCON Bits --------------------------------------------------------

GIE                          EQU     H'0007'
EEIE                         EQU     H'0006'
T0IE                         EQU     H'0005'
INTE                         EQU     H'0004'
RBIE                         EQU     H'0003'
T0IF                         EQU     H'0002'
INTF                         EQU     H'0001'
RBIF                         EQU     H'0000'

;----- OPTION Bits --------------------------------------------------------

NOT_RBPU                     EQU     H'0007'
INTEDG                       EQU     H'0006'
T0CS                         EQU     H'0005'
T0SE                         EQU     H'0004'
PSA                          EQU     H'0003'
PS2                          EQU     H'0002'
PS1                          EQU     H'0001'
PS0                          EQU     H'0000'

;----- EECON1 Bits --------------------------------------------------------

EEIF                         EQU     H'0004'
WRERR                        EQU     H'0003'
WREN                         EQU     H'0002'
WR                           EQU     H'0001'
RD                           EQU     H'0000'

;==========================================================================
;
;       RAM Definition
;
;==========================================================================

        __MAXRAM H'CF'
        __BADRAM H'07', H'50'-H'7F', H'87'

;==========================================================================
;
;       Configuration Bits
;
;==========================================================================

_CP_ON                       EQU     H'000F'
_CP_OFF                      EQU     H'3FFF'
_PWRTE_ON                    EQU     H'3FF7'
_PWRTE_OFF                   EQU     H'3FFF'
_WDT_ON                      EQU     H'3FFF'
_WDT_OFF                     EQU     H'3FFB'
_LP_OSC                      EQU     H'3FFC'
_XT_OSC                      EQU     H'3FFD'
_HS_OSC                      EQU     H'3FFE'
_RC_OSC                      EQU     H'3FFF'


;end of <include> file stuff

;define stuff that microchip in their wisdom re-named.
; this is in case we use the identifiers in the original data sheets by accident

ind0		equ		h'00'		;file address. microchip calls it indf		
rtcc		equ		h'01'		;file address. microchip calls it tmr0
;
;<<option in mpasm is an opcode, and so not allowed as a file address name>>
;option		equ		h'81'		;file address. microchip calls it option_reg
;
to		equ		h'04'		;status. microchip calls it not_to
pd		equ		h'03'		;status. microchip calls it not_pd
rtie		equ		h'05'		;intcon. microchip calls it t0ie
rtif		equ		h'02'		;intcon. microchip calls it t0if
rbpu		equ		h'07'		;option_reg. microchip calls it not_rbpu
rts		equ		h'05'		;option_reg. microchip calls it t0cs
rte		equ		h'04'		;option_reg. microchip calls it t0se



;we have to set the configuration bits
;	__config a & b & c 
;       _rc_osc, _xt_osc, _hs_osc, _lp_osc oscillator type
;       _wdt_on, _wdt_off watchdog timer
;       _cp_on, _cp_off code protect
;       _pwrte_on, _pwrte_off power up timer enable

		__config 	_xt_osc & _wdt_off & _pwrte_on & _cp_off
						;configure pic as desired...

;
;constant equates
;

xtal_freq       =	      	d'4000000'	;crystal frequency
clock           =       	xtal_freq/4	;base operating frequency

;portb definitions (also shadowb)

db7		=		h'07'		;outb lcd db7 (msb)
db6		=		h'06'		;outb lcd db6
db5		=		h'05'		;outb lcd db5
db4		=		h'04'		;outb lcd db4
lcdrs		=		h'03'		;outb lcd rs line (data/!instruction)
lcde		=		h'02'		;outb lcd enable line
pselect		=		h'01'		;outb period select 
						; 0=period  1=px100
periodin	=		h'00'		;inb selected period input

;porta definitions (also acopy)

controla	=		h'00'		;outa 0=x1  1=rangex100
controlb	=		h'01'		;outa period 0=period  1=period/10
zerokey		=		h'02'		;[wt] causes zero compare at current range
downkey		=		h'03'		;[bk] causes move to next lower range
upkey		=		h'04'		;[gn] causes move to next higher range

;character equates

mu		=		h'e4'		;greek letter on lcd for uf
flipchar	=		' '
flopchar	=		h'ff'		;big black box on lcd


;
;eeprom data area 64x8 at h'2100'
;you can store stuff like serial numbers and id codes here.
;use de directives to specify data to be stored.

;in this project eeprom is used only to hold copyright notice.
		
			org	h'2100'			;set data eeprom origin
			
example		de	"Copyright 1999. Designed and Written by Fr. Tom McGahee"


;
;define ram useage
;


;h'0c' is where general purpose sram begins, and ends at h'4f'. room for 68 bytes.

;we use cblock statements to define variable space in sram.

;ram data storage declarations


		cblock	h'0c'

savew				;for inthandler
savestatus			;for inthandler
savefsr				;for inthandler
temp0				;gp: also used for mantissa
temp1				;general purpose
temp2				;general purpose
temp3				;gp: also used for exponent
xmillisec			;register for timer operations
ymillisec			;2nd register for timer operations

acopy				;temp copy of "a" for input of buttons
shadowb				;port b shadow register
wlcdtemp			;lcd temp for w
nibbles				;lcd nibbles and controls are assembled here


x10				;lsb of packed decimal digits.
x32				; later, digits are shifted to make room for
x54				; spaces, comma, and decimal point
x76				; lsb of digit set is low part of byte
x98
xba
xdc
xfe				;msb of decimal digits


flags				;flags (see below for assignments)
autoflags			;autorange flags (see below)

bin24_0				;24 bit binary version of current count
bin24_1
bin24_2

offsetx				;pointer to current offset group

offset_0			;zero adjust and comparison register
offset_1			; for +/- comparisons.
offset_2

offset1_0			;zero adjust for lowest scale
offset1_1
offset1_2

offset2_0			;zero adjust for second scale
offset2_1
offset2_2

offset3_0			;zero adjust for third scale
offset3_1
offset3_2

offset4_0			;zero adjust for highest scale
offset4_1
offset4_2

over_0				;overflow binary counter
over_1				; tells us when sync wait is too long
over_2

		endc

;aliases: these allow alternate name for systems resources.
; it makes reading the program a bit easier while conserving resources

xlsb		=		x10
xmsb		=		xfe
shift		=		temp1
drop		=		wlcdtemp
mantissa	=		temp0
exponent	=		temp3

;shadowb: bit assignments

;LOW 4 BITS ARE SHADOW FOR PORTB   (Some have already been defined)

;periodin	=		h'00'		;inb selected period input
						; (not used, but still registered)
;pselect	=		h'01'		;outb period select 
						; 0=period  1=px100
;lcde		=		h'02'		;outb lcd enable line
;lcdrs		=		h'03'		;outb lcd rs line (data/!instruction)

;flags: bit assignments

intoverflow	=		h'0'		;1=overflow during interrupt routine
;		=		h'1'		;not used
;		=		h'2'		;not used
gotit		=		h'3'		;1=we have data
plusminus	=		h'4'		;1=plus [bin24 > offset]
flipflop	=		h'5'		;flipflop indicator
;		=		h'6'		;not used
;		=		h'7'		;not used

;autoflags: bit assignments

manual		=		h'0'		;1=manual 0=auto
stop		=		h'1'		;1=stop 0=continue operation
rangechange	=		h'2'		;1=range change
toolong		=		h'3'		;1=too long

;conversions used by decimal nibble output routines

n0		=		h'0'
n1		=		h'1'
n2		=		h'2'
n3		=		h'3'
n4		=		h'4'
n5		=		h'5'
n6		=		h'6'
n7		=		h'7'
n8		=		h'8'
n9		=		h'9'
nspace		=		h'a'		;after number is produced
nplus		=		h'b'		; we add in special characters
nminus		=		h'c'
ncomma		=		h'd'
nperiod		=		h'e'
nx		=		h'f'


;
;program  1kx14 eeprom. (h'400') can only be changed via programmer, not on-the-fly.
;                

		
		org     	h'0000'		;set code origin

start		goto		setup		;we have to get past interrupt vector at 0004
		

		
;
;interrupts
;               there is a single interrupt location at 004
;               we must use flags to determine which interrupt...
;               this info is in intcon
;
;intcon register: byte assignments
;
;enables... 1=enable 0=disable
;<7>=gie=global_int_enable
;<6>=eeie=eeprom_int_enable
;<5>=t0ie=t0_int_enable (enables <2> t0if)
;<4>=inte=int_enable (rb0/int) (enables <1> intf)
;<3>=rbie=rb_int_enable (enables <0> rbif)
;
;flags. software reset. 0=reset 1=flagged
;<2>=t0if=t0_int_flag
;<1>=intf=int_flag (rb0/int)
;<0>=rbif=rb_int_flag (rb7-rb4)
;
;upon power up and !mclr!, intcon will contain 0000 000x
;this means that initially all interrupts are disabled.
;
;note: option_reg register is used to program use of tmr0 and wdt
;

		org     h'0004'		;interrupt vector location

inthandler
		;global interrupts automatically disabled on entry!
		;we must save context using a somewhat convoluted scheme
		movwf	savew		;save w register!
		swapf	status,w	;save status! (twisted)
		movwf	savestatus	;(we use swapf so as not to disturb Z!)
		movf	fsr,w		;save fsr! 
		movwf	savefsr
		
;actual interrupt code
		movf	tmr0,w		;save tmr0 in case we need it
		movwf	bin24_0		;save it as low byte
		btfss	intcon,t0if	;if not t0if it should be intf
		goto	intperiod	
inttmr0
		bcf	intcon,t0if	;reset t0if
		incf	bin24_1,f	;update 24 bit binary count
		btfss	status,z	;need carry?
		goto	intreturn	;all done.
		incf	bin24_2,f	;handled carry.
		btfss	status,z	;overflow?
		goto	intreturn	;if not, almost done
		call	set4		;otherwise range to top
		bsf	flags,intoverflow	;set overflow indicator
					;now flow into intperiod to terminate
					; and turn off interrupts

	
;intperiod occurs when rbo/int triggers on edge (intf)
; (it is also entered when there is an overflow!)
intperiod
		bsf	flags,gotit	;tell regular program we have data!
		clrf	intcon		;turn off all interrupts 
					;& clear all interrupt flags

intreturn
		comf	porta,w		;copy inverted porta to w
		andlw	b'00011100'	;check three bits at once
		btfsc	status,z
		goto	intfinish	;if all are low, then none pushed
		movf	porta,w
		movwf	acopy		;save copy of porta for later use
					;sort out the details later...
		
		
intfinish
		movf	savefsr,w	;restore fsr!
		movwf	fsr
		swapf	savestatus,w	;untwist twisted saved status
		movwf	status		;restore normalized status!
		swapf	savew,f		;restore w! first twist nibbles
		swapf	savew,w		;then twist again and place result in w.
					;(how convoluted!)


		retfie                  ;return from interrupt!
					;gie is auto-re-enabled.


;note that jump tables and decoder tables are limited to 256 bytes of program space,
;and care must be taken that tables not cross over page boundaries.
;the limitation is based on the 8 bit addressing scheme employed in tables due to
;the size of w.

;decoder tables take the form:
;label	
;		addwf	pcl,f	;this executes an effective jump forward
;		retlw	'a'	;0 decodes as 'a'
;		retlw	'b'	;1 decodes as 'b'
;		retlw	'c'	;2 decodes as 'c' ... and so on
;
;


;convert special packed bcd+ nibbles into one of 16 8 bit code things

convert
		andlw	b'00001111'	;just the right nibble, please
		addwf	pcl,f		;this executes an effective jump forward 0-15
		retlw	'0'
		retlw	'1'
		retlw	'2'
		retlw	'3'
		retlw	'4'
		retlw	'5'
		retlw	'6'
		retlw	'7'
		retlw	'8'
		retlw	'9'
		retlw	' '		;decode the special stuff, too
		retlw	'+'
		retlw	'-'
		retlw	','
		retlw	'.'
		retlw	'_'		;underscore _ used for non-existent digits

;text for lcd messages
	
sometext			;routine to extract string pieces
		addwf	pcl,f	;this executes an effective jump forward
starttext
begin1text	dt	"PIC CAP METER",0	;put *your* name or message here 16 max
begin2text	dt	"Fr Tom McGahee",0	;put *your* name or message here 16 max
autotext	dt	"AUTORANGING     ",0
overtext	dt	"OVER-RANGE!",0
manual2text	dt	"MANUAL MODE",0
fourspaces	dt	"    "
ufnfpftext	dt	" ",mu,"f  nf  pf",0	;that mu is code for greek letter
zerotext	dt	"ZERO ALL RANGES",0


commontext	=	overtext	;share text to conserve memory
					;(every little "bit" counts!)
					;if you need extra bytes you can
					;reduce the size of the messages,
					;or even eliminate some entirely.
					;but leave fourspaces and ufnfpftext alone,
					;or at least modify them with care!



;               pic16c84 pinouts
;
;       ra2     <1>     <18>    ra1
;       ra3     <2>     <17>    ra0
;  (oc) ra4/tmr0<3>     <16>    osc1/clkin
;       !mclr!  <4>     <15>    osc2/clkout
;       gnd     <5>     <14>    +2 to +6 volts
;       rb0/int <6>     <13>    rb7
;       rb1     <7>     <12>    rb6
;       rb2     <8>     <11>    rb5
;       rb3     <9>     <10>    rb4
;
;osc1 & osc2 allow many types of timing choices. use device command to select.
;
;!mclr! tied high via resistor. use a switch to force it low for a reset.
;
;ra4 becomes the tmr0 external input if option_reg<5>=1. then option_reg<4> selects edge.
;ra4 is not ttl. it is oc out and schmitt in. use pullup resistor if needed.
;
;ra3-ra0 are ttl level. 
;
;rb7-rb0 are ttl. weak pullups can be programmed for inputs if option_reg<7>=0
;rb0/int acts as int pin if intcon<4> inte=1. intcon<1> intf is flag. software reset. 
;rb7-rb4 will generate an interrupt if intcon<3> rbie=1. intcon<0> rbif is flag. software reset.
;


setup		;initialize ports and registers

					;ra4/tmr0<3>, ra3<2>, ra2<1>, ra1<18>, ra0<17>
					;rb7<13>, rb6<12>, rb5<11>, rb4<10>
					;rb3<9>, rb2<8>, rb1<7>, rb0/int<6>

;page 1 stuff includes option_reg, trisa, trisb, eecon1, eecon2

	
		bsf     status,rp0	;allow access to page 1 stuff!
;*************** ignore mplab message[302] 
		movlw	b'00011100'	;set porta direction for i/o pins
		movwf	trisa		;0=output  1=input

		movlw	b'00000001'	;set portb direction for i/o pins
					;using rb0 as interrupt pin.
		movwf	trisb		;0=output  1=input

		bcf     option_reg,not_rbpu	;!rbpu! rb_pullup 0=enabled 1=disabled
						; enabling is based on individual port-latch values
						; we have disabled rb_pullup
						
		bcf     option_reg,intedg	;intedg 0=inc on falling 1=inc on rising
						; <<note: intedg and t0se use opposite definition!>>
						;we are incrementing on falling edge
						; because initial sync is on falling edge
						; and we want full period
						;
						;the edge used is later changed to speed
						;up sync process.

		bcf     option_reg,t0cs		;t0cs timer0clocksource 0=internal clkout 1=ra4/int 
						; (rts in some data sheets)
						; we clear so we can use internal clkout

		bcf     option_reg,t0se		;t0se timer0signaledge 0=inc on rising 1=inc on falling
						; (rte in some data sheets)
						; <<note: intedg and t0se use opposite definition!>>
						; in our application edge makes no difference,
						; so we arbitrarily choose rising edge.

		bsf     option_reg,psa	;psa prescalerassignment 0=tmr0 1=wdt
					;we do not use wdt, but we set prescaler
					;to wdt to allow div by 1 for tmr0!
					
					;ps2-ps0 determine prescalerrate, which is
					;dependent also on whether tmr0 or wdt is selected:
					;wdt from 0-7 is div by 1 2 4 8 16 32 64 128
					;tmr0 from 0-7 is div by 2 4 8 16 32 64 128 256
					;if wdt is assigned prescaler, then tmr0 is div by 1
					; here we will set prescaler to divide by 1 for tmr0
					; by assigning the prescaler to the wdt
					
		bcf     option_reg,ps2	;ps2 set for division by 1
		bcf     option_reg,ps1	;ps1
		bcf     option_reg,ps0	;ps0
;***************
		bcf     status,rp0	;allow access to page 0 stuff again. (back to normal)

;now use movlw/movwf and/or clrf statements to initialize any desired variables

		clrf	flags		;reset all flag bits
		clrf	autoflags
		movlw	b'11111111'	;ensure initial 1's to allow auto-zero!
		movwf	acopy

;ready now to begin  main user program.

mainprog  
		call	lcdreset	;reset lcd, set for 4 bit ops, clear, no cursor
		
;output opening two-line message and wait 2 seconds to allow circuitry to stabilize.

		movlw	begin1text-starttext
		call	textout
		call	lcdhome2
		movlw	begin2text-starttext
		call	textout
		call	delay1000
		call	delay1000
		
		call	lcdclear		;clear lcd and display "ZERO ALL" message
		movlw	zerotext-starttext
		call	textout

;set each range and perform initial Auto-Zero for each scale (click click click)

		call	set1
		call	setstuff
		
		call	set2
		call	setstuff
		
		call	set3
		call	setstuff
		
		call	set4
		call	setstuff

;finished with all the initialization stuff. so here we go loop de loop!

mainloop
		call	periodinit		;sync and acquire a count
		call	checkhit		;check for buttons and process

processdata
		clrf	intcon			;disable interrupts (we have other work to do)
		btfss	flags,intoverflow	;check state of interrupt overflow flag
		goto	convertit		;if no overflow, convert binary, etc.
overflowed
		call	lcdclear		;if interrupt overflow
		movlw	overtext-starttext	; clear display and show message
		call	textout
		goto	mainloop		;try again!

;subroutine to check for buttons being hit while interrupts are off

gethit
		comf	porta,w			;copy inverted porta to w
		andlw	b'00011100'		;check three bits at once
		btfsc	status,z
		return				;if all are low, then none pushed
		movf	porta,w
		movwf	acopy			;save copy of original porta for later use
						;sort out the details later...
		goto	oldhit

;subroutine to handle buttons being hit both in and out of interrupt.
;includes immediate check and check for "old" hits registered in acopy.

checkhit
						;first check for new hit key
		comf	porta,w			;copy inverted porta to w
		andlw	b'00011100'		;check three bits at once
		btfsc	status,z
		goto	oldhit			;if all are low, then none pushed
		movf	porta,w			;otherwise we have a new hit!
		movwf	acopy			;save copy of porta for later use by oldhit
						;liesurely flow into oldhit routine...
oldhit		
		comf	acopy,w			;check if a key was hit (old or new)
		andlw	b'00011100'		;check three bits at once
		btfsc	status,z
		return				;if all are low, then none pushed
		bcf	autoflags,manual	;clear manual so we can use setx routines!
		btfsc	acopy,upkey		;non-inverted original in acopy. is it UP key?
		goto	isitdown		;1 means it was NOT UP key, so check next key
itwasup						;0 means it WAS UP key
		movf	offsetx,w		;ummmm, where ARE we? (what range is current?)
		sublw	offset1_0		;compare by subtracting one from the other
		btfss	status,z		;are we at range 1?
		goto	upto3			;if not, check for others...
upto2
		call	set2 			;if it was 1, change to range 2
		goto	hitdone			;clean up and continue
upto3
		movf	offsetx,w		;it's 2/3/4. Figure out which...
		sublw	offset2_0		;compare by subtracting
		btfss	status,z
		goto	upto4			;if not a match on 2, it must be 3/4
itwas2		
		call	set3			;if it was 2, change range to 3
		goto	hitdone			;clean up and continue
upto4

		call	set4			;if it was 3 go to 4. 
						;if it was already 4, stay at 4. (sticky button)
		goto	hitdone			;clean up and continue

;ahh, it wasn't the UP button. So check for DOWN or ZERO button...

isitdown
		btfsc	acopy,downkey		;non-inverted original in acopy
		goto	iszero			;if it wasn't DOWN, it was ZERO!
itwasdown
		movf	offsetx,w		;Down from *where*? 4/3/2/1
		sublw	offset4_0		;compare by subtracting
		btfss	status,z
		goto	downto2			;if it wasn't 4 it was 3/2/1
downto3
		call	set3			;if it was 4, down range to 3
		goto	hitdone			;clean up and continue
downto2
		movf	offsetx,w		;maybe it is at 3?
		sublw	offset3_0
		btfss	status,z
		goto	downto1			;if not it is 2/1
		
		call	set2			;if it was 3, go down to 2
		goto	hitdone			;clean up and continue
downto1
		call	set1			;2 goes down to 1. sticky button at 1
		goto	hitdone
iszero
		call	periodinit		;before we can perform a zeroing operation
						; we must first acquire a clean reading!
		call	setzerox		;then we do the zero stuff
						;and liesurely flow into the cleanup part below.

;clean up and continue

hitdone	
		comf	porta,w			;copy inverted porta to w
		andlw	b'00011100'		;check three bits at once
		btfss	status,z
		goto	hitdone			;wait for key release!
		movlw	b'11111111'
		movwf	acopy			;"reset" acopy to indicate it is processed.
		bsf	autoflags,manual	;force manual mode
		goto	mainloop		;messes up stack, but who cares?
						;this method allows quicker response
						
						;exiting from a called subroutine by
						;executing a goto is frowned upon by some,
						;but in this case it is the simplest way
						;to speed up the response. it effectively
						;terminates the current loop and goes to the
						;beginning of mainloop.
				
;following subroutine is used by initialization routine to auto-zero each range

setstuff
		call	xmillisecs
		call	periodinit
		call	setzerox
		goto	lcdclear2
		
;subroutines to set circuitry to handle a particular range. Range switching stuff.

set1	
	btfsc	autoflags,manual		;normally in manual mode you don't auto-range.
	return					; this is over-ridden if button is pushed.
	movlw	offset1_0			;offsetx is loaded with appropriate pointer value
	movwf	offsetx
	bsf	shadowb,pselect			;ports are set to turn appropriate external
						; circuitry on/off to select desired range
						
						;some code is shared between set1 and set2
						;strictly to save code space.
set1_2
	movf	shadowb,w
	movwf	portb
	
	bcf	porta,controla
	bcf	porta,controlb
	return

set2		
	btfsc	autoflags,manual
	return
	movlw	offset2_0
	movwf	offsetx
	bcf	shadowb,pselect
	goto	set1_2

set3		
	btfsc	autoflags,manual
	return
	bcf	shadowb,pselect
	movf	shadowb,w
	movwf	portb
	
	bsf	porta,controla
	bcf	porta,controlb
	movlw	offset3_0
	movwf	offsetx
	return

set4		
	btfsc	autoflags,manual
	return
	bcf	shadowb,pselect
	movf	shadowb,w
	movwf	portb
	
	bsf	porta,controla
	bsf	porta,controlb
	movlw	offset4_0
	movwf	offsetx
	return

;subroutine to perform zero function by copying current contents of bin24 counter 
; to local register set. Later the contents of the local set are subtracted from
; whatever the new current count is, and this effects the Zero or Comparison function.

setzerox
		movf	offsetx,w	;recover current offset group
		movwf	fsr		;use indirect addressing
		movf	bin24_0,w	;zero by copying bin24 to offset
		movwf	indf
		incf	fsr,f
		movf	bin24_1,w
		movwf	indf
		incf	fsr,f
		movf	bin24_2,w
		movwf	indf
		return

;subroutine to subtract current offset from current bin24 count.
; allows Zeroing and Comparing.
;
;I do not claim that this implementation is minimal or fastest method to do this.
; I can only say that it works. (I need to get SOME sleep, guys!).
; writing code at 2 AM is not always conducive to achieving conciseness. ZZZZzzzzzz.

subtractoffset
		movf	offset_2,w
		subwf	bin24_2,w		;see which is biggest
		btfsc	status,z
		goto	equal_2			;equal so far
notequal
		btfsc	status,c		;c=1 means bin24 bigger
		goto	bin24bigger
		goto	offsetbigger
equal_2
		movf	offset_1,w
		subwf	bin24_1,w		;see which is biggest
		btfsc	status,z
		goto	equal_1			;equal so far
		goto	notequal		;carry bit tells all...
equal_1

		movf	offset_0,w
		subwf	bin24_0,w		;see which is biggest
		btfsc	status,z
		goto	equal_0			;equal so far
		goto	notequal		;carry bit tells all...
equal_0
		clrf	bin24_0			;so clear bin24
		clrf	bin24_1
		clrf	bin24_2
		return
		
bin24bigger
		bsf	flags,plusminus		;bigger is +
bb0
		movf	offset_0,w
		subwf	bin24_0,f		;place result in bin24
		btfsc	status,z
		goto	bb1			;if zero skip to next byte
		btfsc	status,c
		goto	bb1			;if result was + skip to next byte
bb0neg
		
		
						;handle negative
		movlw	1			;"borrow" one from next byte
		subwf	bin24_1,f
		btfsc	status,c		;if negative (no cy) we need another
						; borrow from next byte.
		goto	bb1			;if positive, do next byte
		
		movlw	1			;then "borrow" one from next byte
		subwf	bin24_2,f


bb1

		movf	offset_1,w		;subtract second byte set
		subwf	bin24_1,f		;place result in bin24
		btfsc	status,z
		goto	bb2			;if zero skip to next byte
		btfsc	status,c
		goto	bb2			;if result was + skip to next byte
bb1neg
						;handle negative
		decf	bin24_2,f		;"borrow" one from next byte
						;that was last byte, so no more borrows
bb2

		movf	offset_2,w		;subtract last byte set
		subwf	bin24_2,f		;place result in bin24
						;this is msb so we is done
		return				;result is in bin24 set


offsetbigger

						;so subtract bin24 from offset
		movf	offset_0,w
						;swap those buggers!
		movwf	temp0			;use temp0
		movf	bin24_0,w
		movwf	offset_0
		movf	temp0,w
		movwf	bin24_0			;swapped. tempset has offset.
		
		movf	offset_1,w
						;swap those buggers!
		movwf	temp1			;use temp1
		movf	bin24_1,w
		movwf	offset_1
		movf	temp1,w
		movwf	bin24_1			;swapped. tempset has offset.
		
		movf	offset_2,w
						;swap those buggers!
		movwf	temp2			;use temp2
		movf	bin24_2,w
		movwf	offset_2
		movf	temp2,w
		movwf	bin24_2			;swapped. temporary set has offset.
		
		call	bin24bigger		;re-use code!
		bcf	flags,plusminus		;except plusminus is different!
						; (hey, it works!)

		movf	temp0,w			;recover offset set
		movwf	offset_0		;0
		

		movf	temp1,w
		movwf	offset_1		;1
		

		movf	temp2,w
		movwf	offset_2		;2
		call 	lcdhome1
		movlw	h'ff'			;flash the negative sign to attract attention
		call	lcdout
		call 	xmillisecs		;just a little flash...
		return
		
;subroutine to convert binary results into human readable format on lcd

convertit
copyzero				;set up indirect adressing to recover stuff
		movf	offsetx,w	;recover current offset group
		movwf	fsr		;use indirect addressing
		movf	indf,w		;get first element
		movwf	offset_0	;copy it
	
		incf	fsr,f		;point to next element
		movf	indf,w		;get 2nd element
		movwf	offset_1	;copy it
	
		incf	fsr,f		;point to next element
		movf	indf,w		;get 3rd element
		movwf	offset_2	;copy it
		
		call	subtractoffset	;perform zero/compare



;bin2dec: converts bin24 to decimal in xlsb->xmsb registers.
;method I chose to use nibble bcd. to get speed up I use a hybrid
;approach to doing the conversions. I designed a decimal adder that
;allows me to add a decimal value by specifying its mantissa and
;exponent values. binary conversions proceed by converting binary
;bits to decimal mantissa/exponent form and then adding the
;mantissa/exponent pieces. I did not choose this method because it
;is elegant, optimal, or whatever. I chose it because it was something
;I had been playing around with as a mental exercise, and I decided
;to use the results of my investigations here in this project.
;so, if some of the code seems a bit strange to you, don't worry.
;it seems a bit strange to me, too! But, hey, it works, and I had
;some mental FUN trying this out.

bin2dec
byte0
		movf	bin24_0,w	;test for zero
		btfsc	status,z
		goto	byte1		;if zero, skip & do next byte
		movwf	temp1		;leave original alone
		
byte0loop				;first the onesies


		call	addtens10	;mantissa is 1 exponent is 0. one.
		decf	temp1,f		;byte is finished when zero
		btfss	status,z
		goto	byte0loop
		
byte1					;then groups of 256
		movf	bin24_1,w	;test for zero
		btfsc	status,z
		goto	byte2		;if zero, skip & do next byte
		movwf	temp1		;leave original alone
byte1loop	
		movlw	6
		call	addtensx0	;exponent of 0 handled special
		
		movlw	5
		movwf	mantissa
		movlw	1
		call	addtensxx
		
		movlw	2
		movwf	mantissa
		movlw	2
		call	addtensxx

		decf	temp1,f		;byte is finished when zero
		btfss	status,z
		goto	byte1loop


byte2					;then groups of 65,536
		movf	bin24_2,w	;test for zero
		btfsc	status,z
		goto	bin2decdone	;if zero, all done.
		movwf	temp1		;leave original alone
byte2loop
		movlw	6
		call	addtensx0
		
		movlw	3
		movwf	mantissa
		movlw	1
		call	addtensxx
		
		movlw	5
		movwf	mantissa
		movlw	2
		call	addtensxx
		
		movlw	5
		movwf	mantissa
		movlw	3
		call	addtensxx
		
		movlw	6
		movwf	mantissa
		movlw	4
		call	addtensxx
	

		decf	temp1,f		;byte is finished when zero
		btfss	status,z
		goto	byte2loop
					;if zero, all done.

bin2decdone				;now we will take the resultant decimal number set
					; and spread it out so we can add the commas and
					; decimal points and other stuff.


		
		movf	offsetx,w	;determine range, because each range has a 
		sublw	offset1_0	; different layout for the digits.
		btfss	status,z
		goto	isit2
its1
		call	sd15x		;I use a shift left from here and drop 
					; method to get the pieces arranged and in place.
		call	sd12p		; for example, the sd15x routine will position
					; us at digit position 15, shift all digits left
					; one place, and then deposit an X at location 15
					; Oh yeah, X has been replaced by an underscore instead.
					; it simply looks neater.
					
					;by the way, all this shift and drop stuff takes
					;place within the bcd register set. once the register
					;set is arranged as we want, then the whole set
					;is read into the lcd display.
		movlw	d'8'
		call	sdxc		
		
		call	sd4c		

		
		call	lcdclear2
		movlw	'1'
		call	lcdout

		call	usual
		movf	bin24_2,w
		sublw	d'7'
		btfsc	status,c
		goto	commonstuff
		call	set4
		goto	commonstuffr

isit2
		
		movf	offsetx,w
		sublw	offset2_0
		btfss	status,z
		goto	isit3
its2		
		call	sd15x		

		call	sd15x		

		call	sd15x		

		call	sd12p		
		
		movlw	d'8'
		call	sdxc		
		
		call	sd4c		

		call	lcdclear2
		movlw	'2'
		call	lcdout

		call	usual
		movf	bin24_2,w
		sublw	d'7'
		btfsc	status,c
		goto	check2low
		call	set4
		goto	commonstuffr
check2low
		movf	bin24_2,w
		btfss	status,z
		goto	commonstuff
		movf	bin24_1,w
		sublw	d'16'
		btfss	status,c
		goto	commonstuff
		call	set1
		goto	commonstuffr


isit3
		
		movf	offsetx,w
		sublw	offset3_0
		btfss	status,z
		goto	its4
its3		

		call	sd15x		;sd is short for shift and drop
		call	sd15x		;sd is short for shift and drop

		call	sd12s		;sd is short for shift and drop
		
		
		movlw	d'8'
		call	sdxp		;sd is short for shift and drop
		

		call	sd4c		;sd is short for shift and drop

		
		call	lcdclear2
		movlw	'3'
		call	lcdout

		call	fourplus
		movf	bin24_2,w
		sublw	d'7'
		btfsc	status,c
		goto	check3low
		call	set4
		goto	commonstuffr
check3low
		movf	bin24_2,w
		btfss	status,z
		goto	commonstuff
		movf	bin24_1,w
		sublw	d'16'
		btfss	status,c
		goto	commonstuff
		call	set2
		goto	commonstuffr


its4		

		call	sd15x		;sd is short for shift and drop
		
		call	sd15x		
		
		call	sd15x		


		call	sd12s		
		
		
		movlw	d'8'
		call	sdxp		
		
		call	sd4c		

		
		call	lcdclear2
		movlw	'4'
		call	lcdout

		call	fourplus

check4low
		movf	bin24_2,w
		btfss	status,z
		goto	commonstuff
		movf	bin24_1,w
		sublw	d'128'
		btfss	status,c
		goto	commonstuff
		call	set3
						;now some shared stuff
commonstuffr
		bsf	autoflags,rangechange


commonstuff
		call	zerooff			;suppress useless zeros, commas, etc.
		call	dataout			;translate & display set on line 1 of lcd
		movlw	0
		call	lcdaddress
		movlw	'+'			;+/- goes in 1st position, line 1
		btfss	flags,plusminus
		movlw	'-'
		call	lcdout
		
		movlw	h'40'+d'13'		;lcdaddress 2nd row
		call	lcdaddress
		btfss	autoflags,manual
		goto	commona
		movlw	'M'			;Manual Mode
		goto	commonstar
		
commona
		movlw	'A'			;Auto Mode
commonstar
		call	lcdout
		movlw	'*'			;two ** indicates a new conversion is completed.
		call	lcdout
		call	lcdout
		call	xmillisecs
		btfss	autoflags,rangechange	;if range changed, say so
		goto	mainloop		;begin next period
		bcf	autoflags,rangechange	;update flag so we don't do this again.
		call	lcdhome2		;2nd line of lcd
		movlw	autotext-starttext	;it *might* be in auto mode
		btfsc	autoflags,manual	;or it *could* be in manual mode
		goto	mstuff
		call	textout			;if auto mode, flash "AUTORANGING"
		goto	mainloop
mstuff
		movlw	1
		call	lcdaddress
		movlw	'M'			;if manual mode, just flash an "M"
		call	lcdout
		goto	mainloop

fourplus
		movlw	fourspaces-starttext	;some ranges have four spaces first...
		call	textout
usual
		movlw	ufnfpftext-starttext	;all have the uf nf stuff
		goto	textout

;zerooff: scans current ascii contents of decimal nibbles and 
; replaces leftmost zeros and comma with spaces. stops at decimal point
; or first non-zero numeric. zero suppresion and some other stuff.

zerooff
		bcf	autoflags,stop	;start off enabled to scan
		movlw	xmsb+1		;scan from xmsb to xlsb
		movwf	fsr		;use indirect addressing
zeroloop
		decf	fsr,f		;update pointer
		movf	fsr,w
		sublw	xlsb-1		;check if past xlsb
		btfsc	status,z
		return			;if past xlsb, we is done here
		
		btfsc	autoflags,stop
		return			;done if stop is set
		
		call	leftcheck
		btfsc	autoflags,stop
		return			;done if stop is set
		call	rightcheck
		goto	zeroloop
leftcheck
		movf	indf,w		;w has copy of byte.
		andlw	b'11110000'	;look at left nibble only
		movwf	temp1		;save original...
		swapf	temp1,w		;check nibble in byte format
		sublw	n0		;was it a leading 0?
		btfsc	status,z
		goto	spaceit		;if so, change to a space.
		
		swapf	temp1,w		;get original back into w
		sublw	ncomma		;was it a comma?
		btfsc	status,z
		goto	spaceit		;if so, change to a space.

		
		swapf	temp1,w		;get original back into w
		sublw	nspace		;was it a space?
		btfsc	status,z
		goto	spaceit		;if so, leave it a space.

		bsf	autoflags,stop	;if anything else, then stop
		return
		
spaceit
		swapf	indf,w		;get swapped version of original
		andlw	b'11110000'	;preserve right nibble (in left)
		iorlw	nspace		;sneak in nspace
		movwf	indf		;save it
		swapf	indf,f		;swap it back to correct form
		return
		


rightcheck
		btfsc	autoflags,stop
		return
		swapf	indf,f		;swap nibbles
		call	leftcheck	;and share code!
		swapf	indf,f		;restore order.
		return

; this is the main synchronization section. It is responsible for assuring that we sync in the
; shortest possible time. After syncing to the period's edge we sort of twiddle our thumbs
; and watch for the flags being set by the interrupt handler. when the flags tell us
; something, we handle it. we handle things like taking excessively long, actually getting
; a flag that says we have a bin24 count accumulated, etc. we also occasionally check
; to see if anyone has pushed any buttons. notice that pushed puttons will terminate
; regular program flow and cause (among other things) a new set of readings to be acquired.

periodinit
		clrf	intcon		;disable all interrupts
		call	clearalldata	;clear binary and decimal versions.
					;also clear over_x counters.
					;data on lcd remains intact.
		btfss	autoflags,toolong	;did interrupt routine say "toolong"?
		goto	periodinit2		;if not, continue...
		call	lcdclear2		;if it *was* toolong, then tell the world!
		movlw	autotext-starttext	; well, it might be in maual or auto modes,
		btfsc	autoflags,manual	; so use different messages for first line
		movlw	manual2text-starttext
		call	textout
		call	delay400
		call	lcdclear2
		movlw	commontext-starttext	;and use some shared text for the rest.
		call	textout

		
periodinit2
		bcf	autoflags,toolong	;if we got here we have handled any toolong,
		bcf	flags,intoverflow	; and any overflow, so clear those flags now!
		btfsc	portb,periodin		;read periodin to find current state of sync
		goto	sync1			;highs go one place, lows another...
		goto	sync2
sync1
		bsf     status,rp0		;allow access to page 1 stuff!

		bcf     option_reg,intedg	;intedg 0=inc on falling 1=inc on rising
						; <<note: intedg and t0se use opposite definition!>>
						;we are incrementing on falling edge
						; because initial sync is on falling edge
						; and we want full period
		bcf     status,rp0		;allow access to page 0 stuff again. (normal)

		btfsc	portb,periodin		;read periodin
		goto	synconlow1
synconhigh1
		call	gethit			;allow buttons
		incf	over_0,f		;twiddle thumbs, but not for *too* long!
		btfss	status,z
		goto	synconhigh1b
		incf	over_1,f
		btfss	status,z
		goto	synconhigh1b
		incf	over_2,f
		btfss	over_2,2
		goto	synconhigh1b
		bsf	autoflags,toolong	;darn! too long! flag it!
		call	set4			;attempt to autorange to highest range.
		goto	periodinit
synconhigh1b
		btfss	portb,periodin		;read periodin
		goto	synconhigh1		;loop until high
synconlow1
		call	gethit			;allow buttons
		incf	over_0,f		;twiddle thumbs, but not for *too* long!
		btfss	status,z
		goto	synconlow1b
		incf	over_1,f
		btfss	status,z
		goto	synconlow1b
		incf	over_2,f
		btfss	over_2,2
		goto	synconlow1b
		bsf	autoflags,toolong	;darn! too long! flag it!
		call	set4			;attempt to autorange to high range
		goto	periodinit

synconlow1b
		btfsc	portb,periodin		;read periodin
		goto	synconlow1		;loop until low
		goto	gotsync

sync2
		bsf     status,rp0		;allow access to page 1 stuff!

		bsf     option_reg,intedg	;intedg 0=inc on falling 1=inc on rising
						; <<note: intedg and t0se use opposite definition!>>
						;we are incrementing on rising edge
						; because initial sync is on rising edge
						; and we want full period
		bcf     status,rp0		;allow access to page 0 stuff again. (normal)




		btfsc	portb,periodin		;read periodin
		goto	synconhigh2
synconlow2
		call	gethit			;allow buttons
		incf	over_0,f		;twiddle thumbs, but not *too* long!
		btfss	status,z
		goto	synconlow2b
		incf	over_1,f
		btfss	status,z
		goto	synconlow2b
		incf	over_2,f
		btfss	over_2,2
		goto	synconlow2b
		bsf	autoflags,toolong	;darn! too long! flag it!
		call	set4			;attempt to autorange to high range
		goto	periodinit


synconlow2b
		btfsc	portb,periodin		;read periodin
		goto	synconlow2		;loop until low
synconhigh2
		call	gethit			;allow buttons
		incf	over_0,f		;twiddle thumbs, but not for *too* long!
		btfss	status,z
		goto	synconhigh2b
		incf	over_1,f
		btfss	status,z
		goto	synconhigh2b
		incf	over_2,f
		btfss	over_2,2
		goto	synconhigh2b
		bsf	autoflags,toolong	;darn! too long! flag it!
		call	set4			;attempt to autorange to high range
		goto	periodinit


synconhigh2b
		btfss	portb,periodin		;read periodin
		goto	synconhigh2		;loop until high
		goto	gotsync
		
gotsync	
		clrf	tmr0			;clear timer to eliminate false triggers
						;periodin just changed.
		clrf	intcon         		;clear any pending interrupt requests
						; also clears all enables including gie.
						; effectively disables all interrupts
		bsf	intcon,inte		;enable rb0/int as int
		bsf     intcon,t0ie		;enable tmr0 interrupt
		
		bcf	flags,gotit		;reset indicators
		bcf	flags,intoverflow
		bsf     intcon,gie		;enable global interrupt enable for now...
		clrf	tmr0			;clear timer now to reduce latency


waitloop
		call	gethit			;allow buttons
		movlw	h'40'+d'14'		;lcdaddress 2nd row
		call	lcdaddress
		btfss	flags,flipflop		;flip/flop black box
		goto	flop			; to indicate major thumb twiddling in progress
flip
		movlw	flipchar
		call	lcdout
		movlw	flopchar
		call	lcdout
		bcf	flags,flipflop
		goto	gotityet
		
flop
		movlw	flopchar
		call	lcdout
		movlw	flipchar
		call	lcdout
		bsf	flags,flipflop

gotityet
		call	xmillisecs		;delay .2 seconds
		btfss	flags,gotit		;is gotit flag up?
		goto	waitloop		;wait seemingly forever....
		return				;WOW! we *finally* have a valid BIN24 ready!
		


;clearalldata: clears data xlsb->xmsb and bin24_0/1/2

clearalldata
		movlw	xlsb		;fsr will point to register set
		movwf	fsr
clearloop
		clrf	indf		;clear a set
		incf	fsr,f		;point to next set
		movf	fsr,w		;copy next set address to w
		sublw	xmsb+1		;done when past xmsb
		btfss	status,z
		goto	clearloop	;clear from xlsb to xmsb

		clrf	bin24_2		;clear 24 bit binary counter
		clrf	bin24_1
		clrf	bin24_0
		
		clrf	over_0		;clear 24 bit overflow counter
		clrf	over_1
		clrf	over_2
		return
		
;shared subroutines to add decimal numbers using mantissa/exponent information
; it may not be pretty, but it works!

addtens10
		movlw	1
addtensx0
		movwf	mantissa
		movlw	0
addtensxx
		movwf	exponent	;exponent
					;flows into addtens

;addtens: enter with mantissa and exponent registers loaded.
;value specified gets added into decimal registers.
;w/flags/mantissa/exponent affected. Returns xlsb->xmsb updated.

addtens
		movf	mantissa,w	;check mantissa value
		btfsc	status,z
		return			;return if nothing to do!
		bcf	status,c	;clear carry
		rrf	exponent,w	;divide exponent by two (nibble oriented)
		addlw	xlsb		;point to proper nibble set
		movwf	fsr		;use it for indirect addressing of nibble sets
		addlw	-(xmsb+1)	;don't go too far!
		btfsc	status,z
		return			;return if all available digits done
		btfsc	exponent,0	;lsb tells us even or odd exponent
		goto	oddstuff
evenstuff
		call	even		;handle rightmost nibble
		incf	exponent,f	;prepare for next higher digit
		goto	addtens		;see if more to do...

oddstuff	
		call	odd		;handle leftmost nibble
		incf	exponent,f	;prepare for next higher digit
		goto	addtens		;see if more to do...
even		
		movlw	b'00001111'	;just look at right nibble
		andwf	indf,w		; of value pointed to
		addwf	mantissa,f	;add mantissa+current value 
					; and save in mantissa
		movf	mantissa,w	;copy into w, too
		addlw	-d'10'		;same as x-10
		btfsc	status,z
		goto	setevenzero	;10-10=0
		btfsc	status,c	
		goto	setevenpositive	;cy=1 if x-10 is positive
setevennegative
		movlw	b'11110000'
		andwf	indf,f		;clear out right nibble
		movf	mantissa,w	;copy mantissa+current value into w
					; (value is less than 10)
		iorwf	indf,f		;now byte contains new right nibble
		clrf	mantissa	;clear carryout pointer
		return			;all done! no carryout.
setevenzero
		movlw	b'11110000'
		andwf	indf,f		;clear out right nibble
		clrf	mantissa	;set mantissa to 1 for carryout.
		incf	mantissa,f	; 0+1=1
		return			;now update next because of carryout
setevenpositive	
		movwf	mantissa	;save positive value
		movlw	b'11110000'
		andwf	indf,f		;clear out right nibble
		movf	mantissa,w	;copy mantissa+current value into w
					; (value is less than 10)
		iorwf	indf,f		;now byte contains new right nibble
		clrf	mantissa	;set mantissa to 1 for carryout.
		incf	mantissa,f	; 0+1=1
		return			;now update next because of carryout
		
odd
		swapf	indf,f		;swap nibbles
		call	even		; and use even routines! how sneaky.
		swapf	indf,f		;swap nibbles back to normal
		return			;return with possible carry in mantissa
		




;sample calling routine
;		call	lcdclear1
;		movlw	onetext-starttext	;note how we subtract starttext address
;		call	textout			; to make an 8 bit address PIC can handle.
;		call	lcdclear2		; sometext routine and attendant data
;		movlw	twotext-starttext	; should be within 1st 256 bytes of program
;		call	textout			; to ensure addressability.


textout		
		movwf	temp1			;save that address!
textloop
		call	sometext		;retlw a byte
		addlw	0			;set z flag if we recovered terminating 0
		btfsc	status,z
		return				;once we got 0 we are done
		call	lcdout			;everything else we send to lcd
		incf	temp1,f			;NEXT!
		movf	temp1,w			;need new address in both temp1 and w
		goto	textloop		;do a whole string of 'em

;routine to output packed bcd+ nibbles

dataout
		call	lcdhome1		;data goes on 1st line of lcd
		movlw	8			;8 bytes = 16 packed bcd+ nibbles
		movwf	temp2			;temp2 holds byte count
		movlw	xmsb
		movwf	fsr			;indirect addressing set to xmsb
dataloop
		swapf	indf,w			;get left nibble, since we are outputting
						; digits etc from left to right (msb->lsb)
		call	convert			;convert 4 bits into full regular ascii
		call	lcdout			;and display it on lcd (including commas, etc.)
		movf	indf,w			;then right nibble
		call	convert
		call	lcdout
		decf	fsr,f			;point to next
		decf	temp2,f
		btfsc	status,z
		return				;done when all 8 bytes done
		goto	dataloop		;otherwise keep going.
		


;these routines are used to "shift and drop" coded items such as commas and decimal
;points into the packed bcd+ decimal set.

;first we have some space-saving attempts that code some of the more popular sd constructs
sd4c
		movlw	d'4'
sdxc
		movwf	shift
		movlw	ncomma
		goto	sd	

sd12s
		movlw	d'12'
		movwf	shift
		movlw	nspace
		goto	sd
sd12p
		movlw	d'12'
sdxp
		movwf	shift
		movlw	nperiod
		goto	sd		
sd15x
		movlw	d'15'
		movwf	shift
		movlw	nx
					;flows into shiftanddrop

;enter with shift position in (shift)temp1 and replacement data in w.
;use fsr method. shift left

;the method used for shifting is to shift a nibble at a time

shiftanddrop
sd					;(I use the shorter label when I have to type.)
		movwf	wlcdtemp	;steal the lcd's temp register to conserve resources.
		movlw	xmsb
		movwf	fsr		;fsr now points to msb
		movf	temp1,w		;check for 'more to do'
		btfsc	status,z	;if position is 0 no shift to do
		goto	stuffleft	; so just stuff data
nextleft
		swapf	indf,f		;otherwise 'shift' nibble left
		movf	indf,w
		andlw	b'11110000'	;clear right nibble
		movwf	indf		;of current byte
		decf	temp1,f		;update shift counter
		btfsc	status,z
		goto	stuffright	;if done, stuff new data
					;otherwise do nextright...
nextright		
		decf	fsr,f		;if not done, shift across bytes
		swapf	indf,w		; copy high nibble into low nibble and to w
		andlw	b'00001111'	;get nibble to be shifted...
		incf	fsr,f		;access 'current' location again
		iorwf	indf,f		;replace with shifted data.
		decf	fsr,f		;point to next byte!
		decf	temp1,f		;update shift counter
		btfsc	status,z
		goto	stuffleft	;if done, stuff new data
		goto	nextleft
		
stuffleft
		movf	indf,w		;recover current byte
		andlw	b'00001111'	;clear left nibble
		movwf	indf		;replace byte
		swapf	wlcdtemp,w	;move replacement nibble into position
		andlw	b'11110000'	;just replace left nibble
		iorwf	indf,f		;done!
		return
		
stuffright
		movf	indf,w		;recover current byte
		andlw	b'11110000'	;clear right nibble
		movwf	indf		;replace byte
		movf	wlcdtemp,w
		andlw	b'00001111'	;just replace right nibble
		iorwf	indf,f		;done!
		return

;a whole bunch of lcd routines to make life easier

initwlcd
		call	wmillisecs	;wait w ms (may be power up condition)
		movlw	b'00110000'
		andlw	b'11110000'	;just high bits first
		movwf	nibbles		;save high nibbles
		movf	shadowb,w	;get control bits from flags
		andlw	b'00001111'	; they are in lower 4 bits
		iorwf	nibbles,w	;save result in w
		movwf	portb		;output high bits and controls...
		bsf	portb,lcde	;begin enable strobe...
		nop			;add extra 1 us delay
		bcf	portb,lcde	;end enable strobe
		return

lcdreset
		movlw	d'16'
		call	initwlcd	;wait 16 ms (may be power up condition)
					;do lcd song and dance init thing
		
		movlw	d'1'
		call	initwlcd	;wait 1 ms (may be power up condition)
					;do lcd song and dance init thing
					
					;most data sheets show 3 initializations,
					;but I have always found 2 to be enough.

		movlw	d'1'
		call	wmillisecs	;wait 1 ms (we don't read busy flag)
		movlw	b'00100000'
		andlw	b'11110000'	;just high bits first
		movwf	nibbles		;save high nibbles
		movf	shadowb,w	;get control bits from flags
		andlw	b'00001111'	; they are in lower 4 bits
		iorwf	nibbles,w	;save result in w
		movwf	portb		;set for 4 bit mode
		bsf	portb,lcde	;begin enable strobe...
		nop			;add extra 1 us delay for good luck
		bcf	portb,lcde	;end enable strobe
		
		movlw	d'1'
		call	wmillisecs	;wait 1 ms to allow it to take
		
					;from here on out we use 4 bit interface!
					;proper delays are built-in.
					
		movlw	b'00101000'	;set 4 bit, 2 lines, 5x7 font
		call	lcdcommand
		
		movlw	b'00001100'	;display on, cursor off,blink off
		call	lcdcommand
		
		call	lcdclear	;clear lcd
		
		movlw	b'00000110'	;increment cursor and no display shift
		call	lcdcommand
		return	
		
;lcd commands: called subroutines. each clobbers w.

lcdclear
		movlw	b'00000001'
		goto	lcdcommand
		
lcdclear1	call	lcdhome1
		call	lcdlineclear
		goto	lcdhome1

lcdclear2	call	lcdhome2
		call	lcdlineclear
		goto	lcdhome2
		
lcdhome1
		movlw	b'00000010'	;home to line 1 and set display to normal
		goto	lcdcommand
		
lcdaddress	
		iorlw	b'10000000'	;append command to address in w.
		goto	lcdcommand	; sets ddram address.

lcdhome2	movlw	b'11000000'	;set ddram address to h'40'
		goto	lcdcommand	; which is line 2
		
lcdlineclear	movlw	d'16'		;lines are 16 characters long
		movwf	temp1
llcloop		movlw	' '		;clear by writing 16 spaces
		call	lcdout
		decf	temp1,f		;update loop count and w
		movf	temp1,f
		btfss	status,z
		goto	llcloop
		return	

;lcdout: called subroutine. w contains data to be sent in two 4 bit nibbles.
;includes delay since we do not read the busy flag. w unaffected.

lcdout
		bsf	shadowb,lcdrs	;set data mode in flags register
		call	lcdnibbles	;do common lcd routine
		return
		



;lcdcommand: called subroutine. w contains data to be sent in two 4 bit nibbles.
;includes delay since we do not read the busy flag. w not affected.

lcdcommand
		bcf	shadowb,lcdrs	;set instruction mode in flags register
		call	lcdnibbles	;do common lcd routine
		movlw	d'4'
		call	wmillisecs	;wait another 4 ms (we don't read busy flag)
		movf	wlcdtemp,w	;we have w back!
		return

;NOTE! lcdnibbles is the low-level interface between data to be displayed and
; the actual hardware implementation of the 4 data bits and control stuff.
; if you implement a different port useage, then this routine would have to
; be modified. pay special attention to the necessary bit masks and port used!
		
lcdnibbles
		movwf	wlcdtemp	;save w!
		movlw	d'1'
		call	_200usecs	;wait min. .2 ms (we don't read busy flag)
		bcf	shadowb,lcde	;set e strobe low (default)
		movf	wlcdtemp,w	;recover original
		andlw	b'11110000'	;just high bits first
		movwf	nibbles		;save high nibbles
		movf	shadowb,w	;get control bits from flags
		andlw	b'00001111'	; they are in lower 4 bits
		iorwf	nibbles,w	;save result in w
		movwf	portb		;output high bits and controls...
		bsf	portb,lcde	;begin enable strobe...
		nop			;add extra 1 us delay
		bcf	portb,lcde	;end enable strobe
		
		swapf	wlcdtemp,f	;swap hi and lo nibbles of original
					;(leave lcdrs bit set as-is)
					
		movf	wlcdtemp,w	;recover swapped nibble
		andlw	b'11110000'	;now low bits are in high bits
		
		movwf	nibbles		;save this nibble (which was low bits)
		movf	shadowb,w	;get control bits from flags
		andlw	b'00001111'	; they are in lower 4 bits
		iorwf	nibbles,w	;save result in w
		movwf	portb		;output low bits...
		bsf	portb,lcde	;begin enable strobe...
		nop			;slight delay
		bcf	portb,lcde	;strobe e goes low now
		
		bsf	shadowb,lcdrs	;default in flags is data mode
		bcf	shadowb,lcde	;default in flags is strobe enable low
		swapf	wlcdtemp,f	;back to normal...
		movf	wlcdtemp,w	;we have w back!
		return
		

;==============================================================================
; _200usecs: (subroutine) : 200 usec delay.
;==============================================================================

	
_200usecs
		movlw	(d'200'-3)/3	;number of loops
		movwf	xmillisec	;(shared register)
_200loop
		decf	xmillisec,f	;count down
		btfss	status,z
		goto	_200loop
		return

delay1000
	call	xmillisecs
delay800
	call	xmillisecs
delay600
	call	xmillisecs
delay400
	call	xmillisecs
				;flow into xmillisecs for last 200 ms delay
;==============================================================================
; xmillisecs: (subroutine) : 200 msec delay. flows into wmillisec.
;==============================================================================

xmillisecs
	movlw	d'200'		;delay...

;==============================================================================
; wmillisecs: (subroutine) : delay for w millisecs. tuned for 4.00 mhz xtal.
;==============================================================================

;called subroutine set to generate delays.
;enter with # of milliseconds to delay in W

wmillisecs

	movwf	xmillisec	;save # of millisecs in w to delay.

	incf	xmillisec,f	;adjust to account for initial decrement.
				;first (outer) loop.
	
wmloop1
	decfsz	xmillisec,f	;update first (outer) loop
	goto	wmloopa		;if more to do, do it.
	
	return			;done when xmillisec=0.
	
wmloopa	
	clrf	ymillisec	;second (inner) loop
				;256 loops (256+1 becomes 0)
	
wmloopb	
	decfsz	ymillisec,f	;update 256 part of inner loop.
	goto	wmloopb		;3 usec per loop
	
	movlw	d'75'+1
	movwf	ymillisec	;75 loops for third loop
	
wmloopc	
	decfsz	ymillisec,f	;update 75 part of inner loop.
	goto	wmloopc		;3 usec per loop
	
	goto	wmloop1		;continue with next outer loop
				
				;total for inner loop sets is 
				;3*(256+75)=993 usec.
				;this together with the overhead
				;makes the total as close to 1000 usec
				;as we need.
				;1000 usec = 1 millisecond
				;hey, it's good enough for *me*!

;==============================================================================
; end of program (at last! Now I can finally get some much needed sleep!!)
;==============================================================================

		end
