; motor servo program for 16f1823
; Bill Thompson - 03-29-2015
; Revision 2 - Fall 2018

; 09/29/2018 many changes to use internal comparator
; 10/23/2018 first running version with internal comparator
; 10/28/2018 code cleanup, no functional changes
; 10/30/2018 enable brownout reset & watchdog timer
; 11/15/2018 reorder interrupt logic for speed
; 11/21/2018 added 3rd speed to encoder
; 11/24/2018 added provision for 8mHz xtal, 32 mHz pll cpu clock
; 12/03/2018 add debounce code for encoder
; 12/28/2012 tweaks for ramp code due to changes in timing from debounce revs
; 03/03/2019 all port write now to latch instead of direct
; 03/04/2019 remove hysteresis on encoder port
; 03/04/2019 add delayed eeprom write enable at startup

;conditional options
;#define 	rampup		;comment this out to disable ramp-up
#define 	mhz8		;comment this out to use 4mhHz xtal

; PORT bit definitions

; port A
;tach0	RA0		;+ input from tachometer
;tach1	RA1		;- input from tachometer
;gpout	RA2		;output to integrator

; port C
;gp78	RC2		;low for 78 speed
;gp33	RC3		;low for 33 speed
                        ;both high for 45 speed

;encode	    RC0-1	;rotary encoder bits
;speed sel  RC2-3
;avail      RC4-5	;available

	list		p=16F1823

; use 8mHz xtal with 4x pll for system clock of 32mHz
; or 4mHz xtal with 4x pll for system clock of 16mHz
; reset pin disabled, lv pgm off
; enable brownout reset and watchdog timer 10/25/2018
; add provision for 8mHz xtal, 32 mHz cpu clock 11/24/2018

; configuration in Microchip convention
#ifdef	mhz8
cfg1	equ	cfg1a & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
cfg1	equ	cfg1a  & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF


; configuration in plain binary
; use _FOSC_XT for 4mHz xtal or _FOSC_HS for 8mHz xtal
#ifdef	mhz8
	__CONFIG _CONFIG1, b'00111110001010' ;0 0 1 11 1 1 0 0 01 010 -- 00 1111 1000 1010 -- 0f8a
	__CONFIG _CONFIG1, b'00111110001001' ;0 0 1 11 1 1 0 0 01 001 -- 00 1111 1000 1001 -- 0f89
	__CONFIG _CONFIG2, b'01101111111111' ;0 1 1 0 1 1 111 1 11 11 -- 01 1011 1111 1111 -- 1bff

;initial timer values for speed determination
;assuming 32mHz timer clock (8mHz xtal)
;or 16mHz timer clock (4mHz xtal)
;these are the values in eeprom the first time
;the pic is powered up. they are then
;updated by encoder adjustments as needed.

;updated these from actual measurement - thorens td-124	04/20/2015
;11/28/2018 add data for 8mHz xtal

#ifdef	mhz8
l78	equ	0x86		;78 low byte
h78	equ	0xea		;78 high byte
l45	equ	0x71		;45 low byte
h45	equ	0xda		;45 high byte
l33	equ	0x3c		;33 low byte
h33	equ	0xcd		;33 high byte


l78	equ	0x53		;78 low byte
h78	equ	0xf5		;78 high byte
l45	equ	0x4b		;45 low byte
h45	equ	0xed		;45 high byte
l33	equ	0xa9		;33 low byte
h33	equ	0xe6		;33 high byte

;speed limits for encoder adjustments (high byte only)
#ifdef	mhz8
hilim	equ	d'240'		;high speed limit
lolim	equ	d'100'		;low speed limit


hilim	equ	d'251'		;high speed limit
lolim	equ	d'200'		;low speed limit

; encoder speed constants
; change step size depending on
; speed of encoder rotation

e_slow	equ	1		;1 step when slow
e_med	equ	8		;8 steps when med.
e_fast	equ	32		;32 steps when fast

;encoder debounce count read encoder mult times
;all reads must agree for valid change
;# of encoder reads = dcount+1
dcount	equ	3		;repeat encoder read 4 times

;ramp speed variables
#ifdef	mhz8
rampspdh	equ	0x40	;8 mHz ramp speed divider 0x01=fast - 0xff=slow
rampspdh	equ	0x20	;4 mHz ramp speed divider 0x01=fast - 0xff=slow

;rampspd can be increased for further slowing of ramp
;it may get shifted left by up to 4 bits at
;higher motor speed so 0x08 is max legal value

rampspd		equ	1	;fine ramp speed 0x08 max.

startspd	equ	0x7f	;high byte motor start speed

; Data Memory

    CBLOCK 0x20 ; all data memory in bank 0

;ram storage for speed parameters
;loaded from eeprom during initialization
;holds speed setting for 3 speeds which may
;be updated by the encoder during operation and
;then updated in eeprom.
spl78				;78

spl45				;45

spl33				;33

;16 bit timer 1 preset storage
;holds countdown value used to determine motor speed.
;loaded from above depending on speed selection

;ramp timer follows timl:timh ramping up and jumping down
;if rampup is enabled so abrupt increase in speed does not throw belts!
rampcnt				;holds ramp speed for countdown
rampcnth			;holds ramp speed divider for countdown
sptemp				;temp for ramp speed calc

;32 mS 8 bit counter for encoder rotation speed

;20 second 16 bit counter for eeprom updates

;storage for rotary encoder operations
;store encoder bits

;debounce storage
elast				;debounce last read 
encnt				;debounce counter

;temp for encoder calculations.

;output encoder data
encspd				;encoder speed 0=no change 1=slow 8=fast
encdir				;encoder direction 0=ccw 1=cw
enclast				;holds last state for debounce

;eeprom read-write address pointer
ee_addr				;eeprom address read/write

;counter for initial integrator reset to make sure
;motor starts up slowly

;delay enabling eeprom writes at startup

;end of data memory
;eeprom address pointers for motor speed data
;these point to physical addr starting at 0xf010
;and are used for eeprom read/write operations
ee	equ	0x10

el78	equ	ee		;78 rpm
eh78	equ	ee+1

el45	equ	ee+2		;45 rpm
eh45	equ	ee+3

el33	equ	ee+4		;33 rpm
eh33	equ	ee+5

; The 16F1823 has 256 bytes of non-volatile EEPROM, starting at address 0xF000
; This is referenced as 0x00-0xFF in read/write operations

USRID	ORG	0X8000			;0x334578 USER id
	DE	0x33
	DE	0X45
	DE	0X78
	DE	0

	DE	"BillThompson.us"	;Place creator at address 0-14

	ORG	0xf010			;initial speed data in eeprom 0x10-0x15
	DE	l78	;78rpm		;programmed at burn time, updated later
	DE	h78			;by encoder
	DE	l45	;45rpm
	DE	h45
	DE	l33	;33rpm
	DE	h33


	ORG     0x0000			;processor reset vector
	GOTO    init			;initialize and start pgm
; there are 2 interrupts, one from the tachometer when it
; changes state, the other from the timer when it overflows.
; the hardware integrator adjusts the motor speed to get a 50%
; duty cycle.
; If the interrupt is not the timer it must be the tachometer.

	ORG      0x0004

;16f1823 has automatic context save for interrupts
;see if interrupt was from the timer
;if yes, output low (integrate up), stop timer, preset timer
;if no, its the tachometer, output high (integrate down), restart timer (already preset)
;write port directly to save banksel 05/19/2015
;reverse interrupt order for speed 11/15/2018
;write to latch, not port for integrator drive 03/03/2019
	banksel 0		;could be in any bank when interrupt happens
	btfss	PIR1,TMR1IF	;a timer interrupt?
	goto	tach1		;no, its tachometer, go handle it
;interrupt is from timer 1 overflow
;RA2 is the only port A output, clearing the whole port
;only affects RA2.
	banksel	LATA		;use latch 03/03/2019
	clrf	LATA		;out low, integrate up
	banksel	0
	bcf	T1CON,TMR1ON	;stop timer

	movf	rtiml,W		;preset timer to countdown value
	movwf	TMR1L		;from speed ramp counter 05/11/2015
	movf	rtimh,W
	movwf	TMR1H

	bcf	PIR1,TMR1IF	;clear timer interrupt
	retfie			;return from interrupt
;interrupt is from tachometer comparator state change
	banksel	LATA		;use latch 03/03/2019
	bsf	LATA,RA2	;out high, integrate down
	banksel	0
	bsf	T1CON,TMR1ON	;restart timer (already preset)
	clrf	PIR2		;clear comparator interrupt
	retfie			;return from interrupt		
; main program starts here
; initialize everything

	banksel	INTCON
	clrf	INTCON		;make sure interrupts are disabled

	banksel	WDTCON		;set up watchdog
	movlw	b'00001110'	;set watchdog 128ms time and disable for now
	movwf	WDTCON

	banksel	OSCCON		;xtal clock, PLL
	movlw	b'10000000'
	movwf	OSCCON

; set analog inputs for comparator
	banksel	ANSELA		;RA0-1 analog inputs for comparator
	movlw	b'00000011'	;all other inputs digital
	movwf	ANSELA
	clrf	ANSELC		;RC port all digital - same bank as ansela
; set up comparator
	banksel	CM1CON0		
	movlw	b'10000110'	;enable comparator w/ normal power, hysteresis
	movwf	CM1CON0
	movlw	b'11000000'	;interrupt on both pos and neg transition
	movwf	CM1CON1		;cm1con1 is in same bank as cm1con0

;set up timer 1 for system clock, no prescaler, no sync
	banksel	T1CON
	movlw	b'01000100'	;tmr1 sys clock, no sync, timer stopped
	movwf	T1CON
	clrf	T1GCON		;no timer 1 gate control
	banksel	OPTION_REG
	movlw	b'00000111'	;global weak pullup,
	movwf	OPTION_REG	;tmr0 sys clock with 1/256 prescale
; weak pullups on RC2,3
	banksel	WPUC		;weak pullups on RC2-3
	movlw	b'001100'	;for speed sel switch
	movwf	WPUC

	clrf	WPUA		;no weak pullups on PORTA

	banksel	PIE1
	clrf	PIE1		;disable all PIE1 interrupts except
	bsf	PIE1,TMR1IE	;enable timer 1 overflow interrupt
	clrf	PIE2		;disable all PIE2 interrupts except
	bsf	PIE2,C1IE	;enable comparator 1 interrupt

	banksel	PIR1
	clrf	PIR1		;clear any pending timer interrupts
	clrf	PIR2		;and comparator interrupts

; set port I/O types and preset encoder to current state
	banksel	TRISA
	movlw 	B'111011'	;RA2 & RC<4-5> = outputs
	movwf	TRISA 		;RA<4-5> inputs for xtal
	movlw	B'001111'	;RC0-1 encoder inputs, RC<2-3> speed sel.inputs
	movwf	TRISC		;and RC4-5 enc.as outputs

	banksel	LATA
	clrf	LATA		;clear port a latches
	clrf	LATC		;and port c
	banksel	0

;start things in a known state	
;timer 1
	banksel	0		;back to bank 0
	clrf	timl		;set rpm timer at start speed
	clrf	rtiml		;and ramp timer

	movlw	startspd	;initial motor start speed (high byte only)
	movwf	timh		;main and ramp timers
	movwf	rtimh
	movlw	rampspd		;set ramp speed
	movwf	rampcnt

;8 bit counter for encoder speed
	clrf	enctim		;set encoder speed counter to slow (0xff)
	decf	enctim,f

;16 bit counter for eeprom update
	clrf	eetiml		;clear (stop) eeprom update counter
	clrf	eetimh	
;delayed eeprom write enable
	movlw	0xff		;preset
	movwf	stdel
;load speed data from eeprom
;(as updated by last adjustments)
	movlw	el78
	call	eerd1		;get 78 data from eeprom
	movwf	spl78		;store it in ram
	movlw	eh78
	call	eerd1		;get 78 data from eeprom
	movwf	sph78		;store it in ram
	movlw	el45
	call	eerd1		;get 45 data from eeprom
	movwf	spl45		;store it in ram
	movlw	eh45
	call	eerd1		;get 45 data from eeprom
	movwf	sph45		;store it in ram
	movlw	el33
	call	eerd1		;get 33 data from eeprom
	movwf	spl33		;store it in ram
	movlw	eh33
	call	eerd1		;get 33 data from eeprom
	movwf	sph33		;store it in ram

;start with integrator low
;integrate down and wait, then start polling loop.
;let integrator ramp up from low at start	
	banksel	LATA
	bsf	LATA,RA2	;integrate down, stop motor
	banksel	0
	clrf	counta		;preset counter
	clrf	countb		;for startup delay
	movlw	d'251'		;4x longer startup delay
	movwf	countc		;when rampup function used

;delay while integrate down for slow start if using slow start ramp

	incfsz	counta,f
	goto	ca
	incfsz	countb,f
	goto	ca
#ifdef	rampup			;longer startup delay if using rampup
	incfsz	countc,f
	goto	ca

	banksel	LATA
	clrf	LATA		;integrate up, start motor
	banksel	0

	banksel	WDTCON		;now start watchdog timer

	banksel	0
	movlw	b'11000000'	;enable global and peripheral interrupts
	movwf	INTCON

;initialization done, main program loop starts here

;speed ramp here to start motor gradually.
;executed if "rampup" is defined
;this may help prevent throwing belts!

;ramp up motor slowly initially or if speed changes
;executes in main loop before polling routines
;rampcnt and rampcnth define how many main loops are executed
;before the ramp code executes. This sets the ramp speed.
;there is additional code to slow down the ramp as motor
;approaches higher speeds.

	banksel	0
	clrwdt			;reset watchdog on each loop

;delay enabling eeprom writes for a bit at startup	
	movf	stdel,W		;read startup delay
	iorlw	0		;zero?
	btfsc	STATUS,Z
	goto	sdel2		;yes, do nothing
sdel1	movf	PORTC,W	 	;yes, read port C to W
	andlw	b'00000011'	;mask all but RC0-1 (encoder)
	movwf	encold		;preset encoder storage to current state
	movwf	encnew
	movwf	elast
	movwf	etmp
	movlw	dcount		;preset debounce
	movwf	encnt
	clrf	eetiml		;stop eeprom timer
	clrf	eetimh

	decf	stdel,f		;count down

	banksel	LATC		;turn off all leds
	clrf	LATC
	banksel	0
sdel2				;continue
#ifdef	rampup			;if ramp-up speed enabled
	decfsz	rampcnth,f	;decrement divider
	goto	poll		;skip ramp if divider not 0
	movlw	rampspdh	;else reset divider
	movwf	rampcnth
	decfsz	rampcnt,f
	goto	poll		;only ramp when ramp counter reaches 0

;skip ramp if tim=rtim	
	movf	rtiml,W		;low byte equal?
	xorwf	timl,W
	btfss	STATUS,Z
	goto	ramp0		;no, may need ramp
	movf	rtimh,W		;yes, high byte equal?
	xorwf	timh,W
	btfsc	STATUS,Z
	goto	poll		;yes, skip ramp code if rtim=tim

ramp0	movlw	rampspd		;not equal, may need ramp, reset ramp counter
	movwf	rampcnt

;see if updated speed is higher or lower, ramp up but not down
	movf	rtiml,W		;get ramp timer
	subwf	timl,W		;subtract from low byte
	movf	rtimh,W
	subwfb	timh,W		;borrow to high byte

	btfss	STATUS,C	;carry set if rtim < tim
	goto	noramp		;don't ramp if equal or more (equal shouldn't get here)
;new speed is faster so ramp up
;slow the up ramp rate at faster speeds 02/22/2016
;rampcnt determines ramp speed - higher is slower
;each left shift results in 1/2 ramp speed

	movf	rtimh,W		;get hi speed byte in W
	andlw	0xf0		;mask low nibble
	addlw	0x40		;add for countdown, check for wrap
	btfss	STATUS,C	;slowdown not needed if no wrap
	goto	ramp1		;skip slowdown if not needed
	movwf	sptemp		;here if ramp slowdown needed
	swapf	sptemp,f	;swap nibbles for loop counter
	incf	sptemp,f	;add 1 to count for decrement
rmploop				;loop for 1/2-1/16 speed
	lslf	rampcnt,f	;shift for 1/2 speed
	decfsz	sptemp,f	;required number of times
	goto	rmploop		;loop for speed/2 again
;ramp-up routine	
;increment 16 bit time by 1
	incfsz	rtiml,f		;ramp up 1 if less
	goto	poll		;go polling if no hi byte incr
	incf	rtimh,f		;increment hi byte if low byte wrapped
	goto	poll		;and go do polling

#endif				;end if ramp-up speed

;here if new speed is lower - jump speed down
	movf	timl,W		;set fully ramped
	movwf	rtiml		;make sure rtim = tim
	movf	timh,W
	movwf	rtimh

;start main polling routines
;polling includes:
;1. servicing counters for encoder rotation speed detection and eeprom update timing
;2. reading encoder
;3. reading speed selector switch
;4. updating speed data based on encoder readings for
;   whichever of 3 speeds is selected

;check our master .5mS counter (timer 0)

	banksel	0
	btfss	INTCON,TMR0IF	;timer 0 overflow?
	goto	poll_ramp	;no loop


;each .5mS, service encoder rate and eeprom update counters

tm0clr				;yes, reset .5ms timer and update counters
;set timing for 8 or 4 mHz xtal 11/26/2018
#ifdef	mhz8			;for 8 mHz xtal
	movlw	b'11110000'	;preset timer 0 to overflow in 16 prescale clocks
#else				;else for 4 mHz xtal
	movlw	b'11111000'	;preset timer 0 to overflow in 8 prescale clocks
	movwf	TMR0		;for a total of .5mS

	bcf	INTCON,TMR0IF	;clear timer0 overflow for next time
;increment counters - there are 2 counters.
;The first is a 16 bit counter for eeprom writing.
;if its 0, its not incremented. When there's an encoder change
;its set to a constant and then counts up for approx 20 sec.
;Every encoder change presets it to the constant for about 20 sec.
;so it starts over. When there's been no encoder change for
;approx 20 sec, it wraps to 0, causes an eeprom write, and then
;stays at 0 until there's another encoder change.
;The second counter determines speed of encoder rotation and
;is used to specify step size for course and fine speed adjustment.
;Every encoder change reads this counter value
;and then resets it. When it reaches 0xff its held there to specify
;the slow rotation. Not timed out causes one of 2 the faster adjustments
;depending on the counter value. It should reach 0xff in approx. 32mS.
;turning the encoder faster therefor results in faster changes per step.

	movf	eetiml,W	;running?
	iorwf	eetimh,W 	;not if 0x0000
	btfss	STATUS,Z	;Z set if not running
	goto	ckee

	banksel	0
	goto	noup	 	;no eeprom update if counter not running
;increment eeprom update counter, when it reaches 0:0
;initiate an eeprom update write and then leave counter
;at 0:0 until there's an encoder change


	banksel	LATC
	bsf	LATC,RC5	;turn on RC5 led
	banksel	0

	incfsz	eetiml,f 	;count up eeprom update counter
	goto	noup	  	;if not 0 no increment hi, no eeprom update
	movlw	0x80		;shorter delay time 03/03/2019
	iorwf	eetimh,f
	incfsz	eetimh,f	;increment hi, timed out if 0
	goto	noup		;if not 0, not timed out. no eeprom update
;update eeprom data if eeprom update timer timed out
	call	eeupd		;if 0:0, update, stay at 0:0 till next encoder change
	banksel	LATC
	clrf	LATC		;turn off all leds when no pending write
	banksel	0
;increment encoder speed timer,
	incfsz	enctim,f	;increment encoder rotation timer
	goto	encrd
	decf	enctim,f	;hold at 0xff if max time


;encoder service - flag if change, store speed and direction
;restart eeprom write timer
;12/03/2018 added debounce code

	banksel 0
	clrf	encspd		;preset set encoder speed at 0 for no change
	movf	PORTC,W	 	;read port C to W
	andlw	b'00000011'	;mask all but RC0-1 (encoder)
	movwf 	etmp		;keep it to tmp
	xorwf	elast,W		;match last read?
	btfsc	STATUS,Z
	goto	en0		;yes, continue

	movf	etmp,W		;no, update last read
	movwf	elast
	movlw	dcount		;restart debounce count
	movwf	encnt
	goto	cks2		;and continue without encoder update
	decfsz	encnt,f		;decrement debounce count
	goto	cks2		;if not 0, continue without encoder update

	movlw	dcount		;if 0, preset debounce count
	movwf	encnt		;and process encoder
	movf	encnew,W	;move the previous "New" value 
	movwf	encold 		;to "Old" file register
	movf	etmp,W		;then move the new value
	movwf	encnew		;to "New" file register
	xorwf	encold,W 	;xor old and new
	btfsc	STATUS,Z 	;xor=0=no change
	goto	cks2	  	;nothing changed, goto speed selector
;encoder changed, restart eeprom update timer on each change
	clrf	eetiml
	clrf	eetimh
	bsf	eetimh,7	;preset eeprom update counter for approx 20 sec. count
;determine encoder rotation speed
	incfsz	enctim,W	;see if encoder timer timed out
	goto	emed	 	;no, set fast or med speed
	banksel	LATC		;turn off RC4 led if slow
	bcf	LATC,RC4
	banksel	0
	movlw	e_slow		;yes, get slow speed constant in W
	goto	ckenc
	banksel	LATC		;turn on RC4 led if not slow
	bsf	LATC,RC4
	banksel	0
	sublw	0x10		;add 3rd speed 11/20/2018
	btfsc	STATUS,C	;encoder turning really fast?
	goto	efast		;yes, go set fast speed
	movlw	e_med		;no, get med speed constant in W
	goto	ckenc
	movlw	e_fast		;get fast speed constant in W
	movwf	encspd		;store encoder speed 
	movlw	b'11000000'	;reset encoder timer for 64 counts (32mS)
	movwf	enctim
;get encoder direction
	clrf	encdir		;assume ccw direction

	movf	encold,W	;move the right
	andlw	b'00000001'	;bit of the
	movwf	etmp2		;OLD value to etmp2
	movf	encnew,W	;move the left
	andlw	b'00000010'	;bit of the
	movwf	etmp		;new value to etmp
	lsrf	etmp,W		;shift right one bit etmp
	xorwf	etmp2,W		;xor them. if it is zero CCW else CW
	btfss	STATUS,Z	;leave at 0 if ccw (skip next)
	bsf	encdir,1	;else make it 1 if CW

;end encoder service

;speed selection and update motor speed
;read turntable speed selector switch	
	banksel	0
	btfss	PORTC,RC2	;78 selected?
	goto	tim78		;yes, do 78
	btfsc	PORTC,RC3	;33 selected?
	goto	tim45		;no, do 45
				;yes, fall into tim33


;speed select routines
;get constants for appropriate speed and update
;speed data based on last encoder change if any
	movf	spl33,W		;pulse width for 33 rpm
	movwf	timl		;get current 33 constants
	movf	sph33,W
	movwf	timh
	call	timadj		;adjust time by encoder data if changed
	movf	timl,W		;update speed data in ram
	movwf	spl33
	movf	timh,w
	movwf	sph33
	goto	poll_ramp	;back to top of main loop
	movf	spl45,W		;pulse width for 45 rpm
	movwf	timl		;get current 45 constants
	movf	sph45,W
	movwf	timh
	call	timadj		;adjust time by encoder data if changed
	movf	timl,W		;update speed data in ram
	movwf	spl45
	movf	timh,w
	movwf	sph45
	goto	poll_ramp	;back to top of main loop
	movf	spl78,W		;pulse width for 78 rpm
	movwf	timl		;get current 78 constants
	movf	sph78,W
	movwf	timh		
	call	timadj		;adjust time by encoder data if changed
	movf	timl,W		;update speed data in ram
	movwf	spl78
	movf	timh,W
	movwf	sph78
	goto	poll_ramp	;back to top of main loop

;end of main polling loop

;subroutine to adjust speed data per encoder
;returns with 16 bit adjusted data in timl, timh
;just return doing nothing if no encoder change
	clrw			;see if encoder speed adj value is 0
	iorwf	encspd,W	;if its 0, no encoder change
	btfsc	STATUS,Z
	return			;do nothing if its 0, else continue

	btfss	encdir,1	;get direction (1 is cw)
	goto	eccw		;skip if not cw
;add if cw
	movf	encspd,W	;get speed adj
	addwf	timl,f		;add to low byte
	addwfc	timh,f		;carry to high byte
;check if over limit, set to limit if so
	movlw	(d'256'-hilim)	;get high time limit (subtr from 256)
	addwf	timh,W		;add to time high byte
	btfss	STATUS,C	;over?
	goto	hok		;no leave alone
	movlw	hilim		;yes, set at high limit
	movwf	timh
	clrf	timl
	clrf	encspd		;zero out enc speed so we don't update again

	;subtract if ccw
	movf	encspd,W	;get speed adj
	subwf	timl,f		;subtract from to low byte
	subwfb	timh,f		;borrow to high byte

;check if under limit, set to limit if so
	movlw	lolim		;get low time limit
	subwf	timh,W		;subtract from time low byte
	btfsc	STATUS,C	;under?
	goto	lok		;no leave alone
	movlw	lolim		;yes, set at low limit
	movwf	timh
	clrf	timl
	clrf	encspd		;zero out enc speed so we don't update again


;subroutine to update eeprom speed data
;do this after encoder activity followed
;by 20 sec of inactivity

;read eeprom and compare to current setting
;only update what's changed

	movlw	el33		;get eeprom data in W
	movwf	ee_addr		;33 low byte
	call	eeread
	xorwf	spl33,W		;changed?
	btfsc	STATUS,Z	
	goto	xh33
	movf	spl33,W		;yes, write new
	call	eewrt

	movlw	eh33		;get eeprom data in W
	movwf	ee_addr		;33 high byte
	call	eeread
	xorwf	sph33,W		;changed?
	btfsc	STATUS,Z	
	goto	xl45
	movf	sph33,W		;yes, write new
	call	eewrt

	movlw	el45		;get eeprom data in W
	movwf	ee_addr		;45 low byte
	call	eeread
	xorwf	spl45,W		;changed?
	btfsc	STATUS,Z	
	goto	xh45
	movf	spl45,W		;yes, write new
	call	eewrt

	movlw	eh45		;get eeprom data in W
	movwf	ee_addr		;45 high byte
	call	eeread
	xorwf	sph45,W		;changed?
	btfsc	STATUS,Z	
	goto	xl78
	movf	sph45,W		;yes, write new
	call	eewrt

	movlw	el78		;get eeprom data in W
	movwf	ee_addr		;78 low byte
	call	eeread
	xorwf	spl78,W		;changed?
	btfsc	STATUS,Z	
	goto	xh78
	movf	spl78,W		;yes, write new
	call	eewrt

	movlw	eh78		;get eeprom data in W
	movwf	ee_addr		;78 high byte
	call	eeread
	xorwf	sph78,W		;changed?
	btfsc	STATUS,Z	
	return			;no, we're done, return
	movf	sph78,W		;yes, write new
	call	eewrt

	return			;and return, all updates done

;eeprom read and write subroutines

;subroutine for EEPROM write
;address in ee_addr, byte to write in W
	banksel	EEDATL
	movwf	EEDATL		;Data Memory Value to write in W

	banksel	0
	movf	ee_addr,W	;get address to write
	banksel	EEADRL
	movwf	EEADRL		;Data Memory Address to write
	bcf	EECON1,CFGS	;Deselect Configuration space
	bcf	EECON1,EEPGD	;Point to DATA memory
	bsf	EECON1,WREN	;Enable writes

	bcf	INTCON,GIE	;Disable Interrupts.
	movlw	0x55
	movwf	EECON2		;Write 55h
	movlw	0xAA
	movwf	EECON2		;Write AAh
	bsf	EECON1,WR	;Set WR bit to begin write
	bsf	INTCON,GIE	;Enable Interrupts
	bcf	EECON1,WREN	;Disable further writes

	btfsc	EECON1,WR	;Wait for write to complete
	goto	eewait

	banksel	0		;done, back to bank 0 default
	return			;and return after write
;subroutine for EEPROM read
;Address to read is in eeaddrl
;returns with value in W
;there are 2 entry points, the first with the addr to read
;in ee_addr, the second with the addr to read in W
eeread				;enter here with addr in ee_addr
	movf	ee_addr,W	;get addr to read in W
eerd1				;enter here with addr in W
	banksel	EEADRL		;or start here with addr to read in W
	movwf	EEADRL		;set addr to read
	bcf	EECON1,CFGS	;Deselect Config space
	bcf	EECON1,EEPGD	;Point to DATA memory
	bsf	EECON1,RD	;EE Read
	movf	EEDATL,W	;W = read data
	banksel	0		;back to bank 0 default
	return			;return with requested data in W