; motor servo program for 16f1823
; Bill Thompson - 03-29-2015
; use this software as you like - its worth at least
; what you paid for it ;-) don't blame me for anything!
; 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/15/2018 port rc4,5 now used for hysteresis on encoder port rc0,1
; 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

;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
;enc. hyst. RC4-5       ;hysteresis drive for encoder inputs

        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
cfg1a   equ     _FOSC_HS & _WDTE_SWDTEN & _PWRTE_ON & _MCLRE_OFF & _CP_OFF
cfg1    equ     cfg1a & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
cfg1a   equ     _FOSC_XT & _WDTE_SWDTEN & _PWRTE_ON & _MCLRE_OFF & _CP_OFF
cfg1    equ     cfg1a  & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF

cfg2    equ     _WRT_OFF & _PLLEN_ON & _STVREN_ON & _BORV_HI & _LVP_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

;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

DATAEE  ORG     0xf000
        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
        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.
        clrf    PORTA           ;out low, integrate up - write port directly 05/19/2015
        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

        clrf    PIR1            ;clear timer interrupt
        retfie                  ;return from interrupt
;interrupt is from tachometer comparator state change
        bsf     PORTA,RA2       ;out high, integrate down - write port directly 05/19/2015
        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. hysteresis outputs

        banksel LATA
        clrf    LATA            ;clear port a latches
        movf    PORTC,W         ;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
        movlw   dcount          ;preset debounce
        movwf   encnt
        swapf   encnew,W        ;shift encoder bits over to rc4,5 in W
        banksel LATC            ;write hysteresis/led bits rc4,5
        movwf   LATC            ;rc4,5 are the only port c outputs
;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  
;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       
        bsf     PORTA,RA2       ;integrate down, stop motor
        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

        clrf    PORTA           ;integrate up, start motor

        banksel WDTCON          ;now start watchdog timer
        bsf     WDTCON,SWDTEN

        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.


        clrwdt                  ;reset watchdog on each loop
;provide encoder inputs with a little positive feedback
;for a schmitt trigger effect
        banksel 0               ;update hysteresis
        movf    PORTC,W         ;read port C to W
        andlw   b'00000011'     ;mask all but RC0-1 (encoder)
        swapf   etmp,W          ;shift encoder bits over to rc4,5 in W
        banksel LATC            ;write hysteresis/led bits rc4,5
        movwf   LATC            ;rc0-3 are inputs
        banksel 0               ;so write doesn't affect them.

#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 0
        incfsz  eetiml,f        ;count up eeprom update counter
        goto    noup            ;if not 0 no increment hi, no eeprom update
        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

;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
;11/15/2018 added hysteresis function
;rc4,5 mirrors rc0,1 encoder inputs
;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
        movlw   e_slow          ;yes, get slow speed constant in W
        goto    ckenc
        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