;;;;;;; P5 for QwikFlash board ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;
; Name: Shane Connelly
; Name: Grant Lohsen
; Handle: WSCL
;
; PIC18F452's internal clock period is 0.4 microseconds.
; Use Timer2 for a loop time of approximately ten milliseconds (see Figure 16-3)
; with A=16, B=223, C=7 for 24976 internal clock cycles = 9990.4 microseconds.
; Toggle B0 output every ten milliseconds for measuring looptime with scope.
; Step stepper motor at a rate of 100 steps per second.
; Blink "Alive" LED every two seconds.
; Also, the code reads the value of the potentiometer every 1 second
; After it reads the potentiometer, it sets the stepper motor to step n times per
; second where n = 12 * (the potentiometer value from 0-8). You may enter the number
; of steps to take with the keypad, as well as the direction to turn (*: clockwise,
; #: counterclockwise).
; If a key is pressed the LCD display updates to show the (up to) 4 digit number that
; has been entered (in the lower right corner). if it is more than 4 digits it clears 
; the value and starts again. the lower left corner of the LCD display shows the value
; of the potentiometer(0-8) multiplied by 12. The top center of the LCD display shows
; the (up to 4 digit) position of the motor and the direction of the position 
; (+=cw, -=ccw). None of the displays show leading zeros on a number, so 06 is displayed
; as 6.
; When POT value is 00, instead of displaying the status of the stepper motor and related 
; information, instead display columns of characters ranging from (upper nibble) 0 to F as
; set by the RPG and (lower nibble) 0 to f (inclusive). 
; Also, checks the RPG for changes every 10th of a second. 
; ALSO, once the pot is no longer 0, resume operation as before. 
;
;;;;;;; Program hierarchy ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Mainline
;   Initial
;     InitLCD
;       LoopTime
;   ClearDisplay
;     SendByte
;       T40
;   PotDisplay
;     ReadPot
;     ClearDisplay
;       SendByte
;         T40
;     RateDisplay
;       LoadBuffer
;         FXD1608U
;       DisplayV
;         T40
;     ASCIIDISP
;       TXbyte
;       SendByte
;         T40
;   Keypad
;     ReadKeypad
;     ResetKeypad
;       ByteOutBuffer
;         DisplayV
;           T40
;     ByteOutBuffer
;       DisplayV
;         T40
;   ControlStepping
;     LoadBuffer
;       FXD1608U
;     DisplayV
;       T40
;   RPG
;   ASCIIDISP
;     TXbyte
;     SendByte
;       T40
;   BlinkAlive
;   LoopTime
;
;;;;;;; Assembler directives ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

        list  P=PIC18F452, F=INHX32, C=160, N=0, ST=OFF, MM=OFF, R=DEC, X=ON
        #include P18F452.inc
        __CONFIG  _CONFIG1H, _HS_OSC_1H  ;HS oscillator
        __CONFIG  _CONFIG2L, _PWRT_ON_2L & _BOR_ON_2L & _BORV_42_2L  ;Reset
        __CONFIG  _CONFIG2H, _WDT_OFF_2H  ;Watchdog timer disabled
        __CONFIG  _CONFIG3H, _CCP2MX_ON_3H  ;CCP2 to RC1 (rather than to RB3)
        __CONFIG  _CONFIG4L, _LVP_OFF_4L  ;RB5 enabled for I/O

;;;;;;; Variables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

        cblock  0x000           ;Beginning of Access RAM
        COUNT
        ALIVECNT                ;Counter for blinking "Alive" LED
        HUNDRED                 ;Counter for reading the potentiometer value
        POTVALUE                ;Holds the raw potentiometer value
        SMALLPOT                ;Holds the scaled value of the potentiometer
        STEPRATE                ;The rate in steps/second of the stepper motor
        ACCUM                   ;The accumulator for the motor
        NUML                    ;Low byte of number entered
        NUMH                    ;High byte of number entered
        NUMSTPSL                ;Low byte of NUMSTPS and the number of steps to take
        NUMSTPSH                ;High byte of numstps
        POSH                    ;High byte of the stepper motor position
        POSL                    ;Low byte of the stepper motor position
        DISP_POS:7              ;Display information for stepper motor position
        DISP_RATE:4             ;Display information for stepper motor rate
        DISP_BUF:6              ;Display information for numerical input
        KEYSTRG:4               ;A 4-byte string to hold digit keycodes entered in succession
        FLAGS                   ;The bits of flags are used as state bits
        DISPLAYNUMH             ;High byte of the number to be displayed on the LCD
        DISPLAYNUML             ;Low byte of the number to be displayed on the LCD
        DISPLAYPOS              ;Position of the LCD display number in RAM
        DISPLAYLEN              ;Length of the LCD display number in RAM
        DISPLAYSIGN             ;Sign of the LCD display
        DELRPG                  ;change in RPG value (-1, 0, +1)
        RPGVALUE                ;current value of the RPG
        ASCIIENABLED            ;determine whether it is displaying the ASCII or the status information
        OUTCHAR                 ;character to output the ASCII value to the display
        BGCOUNT                 ;count the number of characters on the bargraph/ascii display
        OLDPORTD                ;last value of RPG display
        NEWPORTD                ;new value of the RPG display
        PBSTATE					;pushbutton state
        endc
 
        #include <C:\MATH18\MATHVARS.inc>


     
;;;;;;; Macro definitions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

MOVLF   macro  literal,dest
        movlw  literal
        movwf  dest
        endm
        
POINT   macro  stringname
        MOVLF  high stringname, TBLPTRH
        MOVLF  low stringname, TBLPTRL
        endm

;;;;;;; Vectors ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

        org  0x0000             ;Reset vector
        goto  Mainline

        org  0x0008             ;High priority interrupt vector
        goto  $                 ;Trap

        org  0x0018             ;Low priority interrupt vector
        goto  $                 ;Trap

;;;;;;; Mainline program ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Mainline
        call  Initial           ;Initialize everything
        
        LOOP_
          btg  PORTB,RB0        ;Toggle pin, to support measuring loop time
          bsf PORTC, RC2        ;Toggle pin to support measuring useful function time
                                ;call  ResetKeypad
          call  PotDisplay      ;run the potentiometer read and display functions
          call  Keypad
          call  ControlStepping  ;control the motor step rate
          call  RPG             ;detect changes in RPG value
          movf DELRPG,w         ;if a change in RPG value AND asciienabled = 1 then display 
          IF_ .NZ.              ;   ascii column
            movf ASCIIENABLED
            IF_ .NZ.
              call ASCIIDISP
            ENDIF_
          ENDIF_
          IF_ PORTD,RD3 == 0		;check if the pushbutton is pushed
            movf ASCIIENABLED,W		;check if were in ASCII display mode
            IF_ .Z.					;if not,
              movlw 0				;reset the position to 0
              movwf POSH
              movwf POSL
              call InitStatus
            ELSE_					;otherwise
              movlw 4				;set the ASCII display to the 4th set
              movwf RPGVALUE
              call ASCIIDISP
            ENDIF_
            bsf PORTA,RA1			;light up the LED when the push button is pressed
          ELSE_
            bcf PORTA,RA1			;kill the LED when the pushbutton is released
          ENDIF_
          call  BlinkAlive      ;Blink "Alive" LED
          bcf PORTC, RC2        ;set the pin low to end timing
          call  LoopTime        ;Wait for ten milliseconds to be up
        ENDLOOP_

;;;;;;; Initial subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine performs all initializations of variables and registers.

Initial
        MOVLF  B'01001110',ADCON1  ;Enable PORTA & PORTE digital I/O pins
        MOVLF  B'01100001',ADCON0  ;Select pot input to ADC
        MOVLF  B'11100001',TRISA  ;Set I/O for PORTA
        MOVLF  B'11001100',TRISB  ;Set I/O for PORTB
        MOVLF  B'11010000',TRISC  ;Set I/O for PORTC
        MOVLF  B'00001111',TRISD  ;Set I/O for PORTD
        MOVLF  B'00000100',TRISE  ;Set I/O for PORTE
        MOVLF  B'00010000',PORTA  ;Turn off all four LEDs driven from PORTA
        bsf  PORTB,RB5          ;Step direction is CW
        MOVLF  B'10110111',T2CON  ;Set up Timer2 (10 millisecond looptime)
        MOVLF  222, PR2
        ;MOVLF B'00001000', PBSTATE		;set I/O for pushbutton
        MOVLF 200,ALIVECNT      ;set the time/loop
        MOVLF 100,HUNDRED       ;set the time/loop
        movlw 0					;prime asciienabled
        movwf ASCIIENABLED
        call  InitLCD
        
        call ResetKeypad
        call InitStatus
        
        return

        
        
;;;;;;; InitStatus subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Initialize the status screen for the LCD


InitStatus        
        call ClearDisplay       ;clears the display
        call ByteOutBuffer
        movff POSH,DISPLAYNUMH  ;Move the position codes into the buffer variables
        movff POSL,DISPLAYNUML
        call StepOutput
        call PotDisplay			;prime potdisplay
        return
        
;;;;;;; InitLCD subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Initialize the Optrex 8x2 character LCD.
; First wait for 0.1 second, to get past display's power-on reset time.

InitLCD
        MOVLF  10,COUNT         ;Wait 0.1 second
        REPEAT_
          rcall  LoopTime       ;Call LoopTime 10 times
          decf  COUNT,F
        UNTIL_  .Z.

        bcf  PORTE,0            ;RS=0 for command
        POINT  LCDstr           ;Set up table pointer to initialization string
        tblrd*                  ;Get first byte from string into TABLAT
        REPEAT_
          bsf  PORTE,1          ;Drive E high
          movff  TABLAT,PORTD   ;Send upper nibble
          bcf  PORTE,1          ;Drive E low so LCD will process input
          rcall  LoopTime       ;Wait ten milliseconds
          bsf  PORTE,1          ;Drive E high
          swapf  TABLAT,W       ;Swap nibbles
          movwf  PORTD          ;Send lower nibble
          bcf  PORTE,1          ;Drive E low so LCD will process input
          rcall  LoopTime       ;Wait ten milliseconds
          tblrd+*               ;Increment pointer and get next byte
          movf  TABLAT,F        ;Is it zero?
        UNTIL_  .Z.
        return
        
;;;;;;;;DisplayC subroutine;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine is called with TBLPTR containing the address of a constant
; display string.  It sends the bytes of the string to the LCD.  The first
; byte sets the cursor position.  The remaining bytes are displayed, beginning
; at that position.
; This subroutine expects a normal one-byte cursor-positioning code, 0xhh, or
; an occasionally used two-byte cursor-positioning code of the form 0x00hh.

DisplayC
        bcf  PORTE,0            ;Drive RS pin low for cursor-positioning code
        tblrd*                  ;Get byte from string into TABLAT
        movf  TABLAT,F          ;Check for leading zero byte
        IF_  .Z.
          tblrd+*               ;If zero, get next byte
        ENDIF_
        REPEAT_
          bsf  PORTE,1          ;Drive E pin high
          movff  TABLAT,PORTD   ;Send upper nibble
          bcf  PORTE,1          ;Drive E pin low so LCD will accept nibble
          bsf  PORTE,1          ;Drive E pin high again
          swapf  TABLAT,W       ;Swap nibbles
          movwf  PORTD          ;Write lower nibble
          bcf  PORTE,1          ;Drive E pin low so LCD will process byte
          rcall  T40            ;Wait 40 usec
          bsf  PORTE,0          ;Drive RS pin high for displayable characters
          tblrd+*               ;Increment pointer, then get next byte
          movf  TABLAT,F        ;Is it zero?
        UNTIL_  .Z.
        return
        
;;;;;;; DisplayV subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine is called with FSR0 containing the address of a variable
; display string.  It sends the bytes of the string to the LCD.  The first
; byte sets the cursor position.  The remaining bytes are displayed, beginning
; at that position.

DisplayV
        bcf  PORTE,0            ;Drive RS pin low for cursor positioning code
        REPEAT_
          bsf  PORTE,1          ;Drive E pin high
          movff  INDF0,PORTD    ;Send upper nibble
          bcf  PORTE,1          ;Drive E pin low so LCD will accept nibble
          bsf  PORTE,1          ;Drive E pin high again
          swapf  INDF0,W        ;Swap nibbles
          movwf  PORTD          ;Write lower nibble
          bcf  PORTE,1          ;Drive E pin low so LCD will process byte
          rcall  T40            ;Wait 40 usec
          bsf  PORTE,0          ;Drive RS pin high for displayable characters
          movf  PREINC0,W       ;Increment pointer, then get next byte
        UNTIL_  .Z.             ;Is it zero?
        return

;;;;;;; T40 subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Pause for 40 microseconds  or 40/0.4 = 100 clock cycles.
; Assumes 10/4 = 2.5 MHz internal clock rate.

T40
        movlw  100/3            ;Each REPEAT loop takes 3 cycles
        movwf  COUNT
        REPEAT_
          decf  COUNT,F
        UNTIL_  .Z.
        return
        

        
;;;;;;; LoadBuffer subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine loads data into a buffer formatted correctly for the LCD display.
; It assumes the position of the data is loaded via lfsr 0

LoadBuffer
        clrf POSTDEC0           ;first set the 0x00 terminator
        
        IF_ DISPLAYNUMH,7 == 1  ;check for a negative number
          negf DISPLAYNUML      ;if it is, form the 2's compliment
          comf DISPLAYNUMH, F
          IF_ .C.
            incf DISPLAYNUMH, F
          ENDIF_
        ENDIF_
        REPEAT_
          movlw 10              ;We need to divide 10 from the the value to produce
                                ;the division
          movwf BARGB0          ;Store 10 in the B argument of divide (A/B)
          
          movff DISPLAYNUMH, AARGB0  ;Store the high byte in the value of A for the divide
          movff DISPLAYNUML, AARGB1  ;Store the low byte in the value of A for the divide
          call FXD1608U         ;Perform the division function
          movlw 0x30            ;Load an ASCII adjuster
          addwf REMB0, W        ;To add to the remainder of the division
          movwf POSTDEC0        ;Move the value into the buffer and decrement the position
          movff AARGB0, DISPLAYNUMH  ;Set the remaining value for further division
          movff AARGB1, DISPLAYNUML  ;Set the remaining value for further division
          decf DISPLAYLEN,F     ;decrement the remaining length
          IF_ .NZ.
            movf AARGB0,W
            iorwf AARGB1,W
          ENDIF_
        UNTIL_ .Z.              ;until it becomes zero
        
        movf DISPLAYLEN,F       ;check the display length to see if we need spaces
        WHILE_ .NZ.
          movlw 0x20
          movwf POSTDEC0
          decf DISPLAYLEN,F     ;decrement the remaining length
        ENDWHILE_
        return


;;;;;;; PotDisplay subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine briefly displays a range between 0 and 8 of the Potentiometer.

PotDisplay
        decf  HUNDRED,F         ;Decrement hundred loop counter and return if not zero
        IF_ .Z.
          MOVLF  100,HUNDRED    ;Reinitialize BLNKCNT
          call ReadPot          ;sends the potentiometers value to the ADC
          movf POTVALUE,W       ;copies potvalue to the wreg
          mullw 9               ;multiplies potvalue(in wreg) by 9
          movff PRODH, SMALLPOT  ;get the upper byte and store it in smallpot
          movf SMALLPOT, W      ; multiply the potentiometer value by 12 to generate the 
          mullw 12              ; motor stepping rate between 0 and 96 (in steps/second)
          movff PRODL, STEPRATE  ;save the stepping rate in variable STEPRATE
          IF_ .NZ.
            movf ASCIIENABLED,w  ;ensure that this is the run only when changing from column display
            IF_ .NZ.
              movlw 0           ; set asciienabled=0 (show status info)
              movwf ASCIIENABLED  
              call InitStatus	;initalize the status display
              
            ENDIF_
            call RateDisplay    ;Show the motor rate on the computer
          ELSE_
            movf ASCIIENABLED,w
            
            IF_ .Z.
              movlw 1           ; set asciienabled=1 (show columns of characters)
              movwf ASCIIENABLED
              movlw 0           ;set RPGVALUE = 0
              movwf RPGVALUE
              call ASCIIDISP
            ENDIF_
          ENDIF_
        ENDIF_
        return
        
;;;;;;; ReadPot subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine reads the potentiometer and puts the upper byte into ADRESH

ReadPot
        bcf ADCON0,3            ;select pot input to ADC
        bcf ADCON0,4            ;
        bcf ADCON1,ADFM         ;Left Justify data
        movlw 15                ;initalize wait loop
        REPEAT_                 ;wait loop for 15 iterations
          decf WREG, F
        UNTIL_ .Z.
        bsf ADCON0,GO_DONE      ;initiate Conversation
        REPEAT_
        UNTIL_  ADCON0,GO_DONE == 0  ;wait for completion of conversion
        movff ADRESH, POTVALUE  ;sends the ADC value to POTVALUE
        return                  ;return with result in ADRESH
        
;;;;;;; RateDisplay subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine sends the value of the variable STEPRATE to the computer as ASCII digits

RateDisplay
        movlw 0                 ;move 0 into the DISPLAYNUMH because we wont use it
        movwf DISPLAYNUMH
        movff STEPRATE,DISPLAYNUML  ;set STEPRATE into the lower byte of the display number
        movlw 2                 ;we will be using a display length of 2 numbers
        movwf DISPLAYLEN
        lfsr 0,DISP_RATE+3      ;move the third byte of DISP_RATE for buffer processing
        call LoadBuffer         ;process the buffer codes
        movlw 0xc0              ;add the LCD control code
        movwf DISP_RATE
        lfsr 0,DISP_RATE        ;finally, call the LCD output
        movf ASCIIENABLED, w	;ensure we are not displaying ASCII characters
        IF_ .Z.
          call DisplayV
        ENDIF_
                                ;legacy code---
                                ;movlw 10                ;We need to divide 10 from the STEPRATE to produce
                                ;an ASCII value to transmit
                                ;movwf BARGB0            ;Store 10 in the B argument of divide (A/B)
                                ;movff STEPRATE, AARGB0  ;Store the step rate in the value of A for the divide
                                ;call FXD0808U           ;Perform the division function
                                ;movlw 0x0d              ;This sends the loads the carriage return value
                                ;call TXbyte             ;And send it to the computer
                                ;movlw 0x30              ;Load an ASCII adjuster
                                ;addwf AARGB0, W         ;To add to the integer result of the division
                                ;call TXbyte             ;Send this ASCII value to the computer
                                ;movlw 0x30              ;Load an ASCII adjuster
                                ;addwf REMB0, W          ;To add to the remainder of the division
                                ;call TXbyte             ;Send this ASCII value to the computer
        return
        
;;;;;;; TXbyte subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine first waits on the UART if a byte is in the process of being
; sent.  Then it sends the content of WREG to the PC.

TXbyte
        REPEAT_                 ;If an earlier transmission is still
        UNTIL_ PIR1,TXIF == 1   ;in progress then wait
        movwf TXREG             ;send new byte from wreg
        return	

;;;;;;; ControlStepping subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine sets the stepper motor speed in accordance to the STEPRATE variable
; Also, this subroutine steps the motor the number of steps specified by the NUMSTPS(H:L) variable

ControlStepping
        movf NUMSTPSL,w         ;xor numstpsl with numstpsh to see if it is an overall zero value number
        iorwf NUMSTPSH, w
        IF_ .NZ.                ;check for non-zero value in xor of NUMSTP(H:L)
          movf STEPRATE, W      ;Add STEPRATE into the step motor accumulator
          addwf ACCUM, F
          IF_ .C.               ;If there is an overflow from the accumulator then...
            IF_ NUMSTPSH,7 == 1  ;check for ccw
              decf POSL,f       ;decrement the position
              IF_ .B.
                decf POSH, f
              ENDIF_
              
              bcf  PORTB,RB5    ;set direction to CCW
              incf NUMSTPSL,f   ;increment the number of steps left to be taken
              IF_ .C.
                incf NUMSTPSH, f		
              ENDIF_
            ELSE_               ;check for cw
              incf POSL,f       ;increment the current stepper motor position
              IF_ .C.
                incf POSH, f		
              ENDIF_
              
              bsf  PORTB,RB5    ;set step direction to CW
              decf NUMSTPSL,f   ;decrement the number of steps left to be taken
              IF_ .B.
                decf NUMSTPSH, f
              ENDIF_
            ENDIF_
            btg PORTB,RB1       ;Toggle RB1 for motor time measurement
            bsf  PORTB,RB4      ;Set the stepper motor movement
            bcf  PORTB,RB4      ;Clear the stepper motor movement
            movlw 100           ;Move 100 into WREG for subtraction
            subwf ACCUM,F       ;Subtract 100 to drop the counter down from the overflow
            movff POSH,DISPLAYNUMH  ;Move the position codes into the buffer variables
            movff POSL,DISPLAYNUML
            call StepOutput	;display step value
          ENDIF_
        ENDIF_
        
        return
        
;;;;;;; BlinkAlive subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine briefly blinks the LED next to the PIC every two seconds.

BlinkAlive
        bsf  PORTA,RA4          ;Turn off LED
        decf  ALIVECNT,F        ;Decrement loop counter and return if not zero
        IF_ .Z.
          MOVLF  200,ALIVECNT   ;Reinitialize BLNKCNT
          bcf  PORTA,RA4        ;Turn on LED for ten milliseconds every 2 sec
        ENDIF_
        return

        
;;;;;;; ReadKeypad subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;Reads the input from the keypad

ReadKeypad
        bsf ADCON0,3            ;select keypad input to ADC
        bsf ADCON0,4            ;
        bsf ADCON1,ADFM         ;Right Justify Data
        movlw 15                ;initalize wait loop
        REPEAT_                 ;wait loop for 35 iterations
          decf WREG, F
        UNTIL_ .Z.
        bsf ADCON0,GO_DONE      ;initiate Conversation
        REPEAT_
        UNTIL_  ADCON0,GO_DONE == 0  ;wait for completion of conversion
        return                  ;return with result in ADRESH

;;;;;;; SendStrg subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine sends keypress values to the computer.

SendStrg
        movlw 0x0d              ;This sends the carriage return value
        call TXbyte             ;And send it to the computer
        movlw 0x0a              ;This sends the linefeed value
        call TXbyte             ;And send it to the computer
        movf SIGN,w             ;load sign into wreg			
        call TXbyte             ;And send it to the computer
        lfsr 0, KEYSTRG         ;puts address of keystring into memory
        movf KEYSTRG,w          ;loads digit of keystring into wreg
        REPEAT_
          call TXbyte           ;And send it to the computer
          movf PREINC0,W  
        UNTIL_ .Z.
        movlw 0x0d              ;This sends  the carriage return value
        call TXbyte             ;And send it to the computer
        movlw 0x0a              ;This sends  the linefeed value
        call TXbyte             ;And send it to the computer
        return

        
;;;;;;; ResetKeypad subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine clears keypress information.

ResetKeypad
        
        clrf NUMH               ;clear numh variable
        clrf NUML               ;clear numl variable
        movlw 0x20
        movwf KEYSTRG           ;clear keystrng locations in ram
        movwf KEYSTRG+1
        movwf KEYSTRG+2
        movlw 0x30
        movwf KEYSTRG+3        
        call ByteOutBuffer

                                ;lfsr 0,KEYSTRG          ;remap the location of keystrng
        return
        
        
;;;;;;; Keypad subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine checks for a new keypress.
; This is done by implementing a statemachine which acts both as a button debouncer
; as well as a check to see if the key has been released (thus a new key is being entered).
;

Keypad
        call ReadKeypad
        
        IF_ ADRESH,0 == 1       ;if bit 0 of adresh is 1 then clear bits 0 and 1 of flags
          bcf FLAGS,0
          bcf FLAGS,1
        ELSE_
          IF_ FLAGS,0 == 0
            IF_ FLAGS,1 == 0    ;if FLAGS = 00
              bsf FLAGS,0       ;set flags bit 0 to 1         
            ENDIF_
          ELSE_    		
            IF_ FLAGS,1 == 0    ;if FLAGS = 01
              bsf FLAGS,1       ;set flags bit 1 to 1
              movff ADRESL, TBLPTRL  ;copy adresl to tblptrl
              movlw 0x5f        ;put 0x5f into tblptrh
              movwf TBLPTRH
              tblrd*            ;copies keyvalue of pressed button into tablat
              
              movlw 0x2a        ;xor the tablat with 2a resulting in a 0 if *(0x2a) is pressed
              xorwf TABLAT,w
              IF_ .Z.           ;if * is pressed
                movlw 0x2b      ;store + in sign
                movwf SIGN
                movf NUML, w    ;adds the NUMH:NUML values to the NUMSTPSH:NUMSTPSL values storing in NUMH:NUML
                addwf NUMSTPSL, f	
                movf NUMH, w
                addwfc NUMSTPSH,f
;                call SendStrg   ;sends values to computer
                call ResetKeypad  ;clears numh:numl
              ELSE_
                movlw 0x23      ;23				;xor the tablat with 23 resulting in a 0 if #(0x23) is pressed
                xorwf TABLAT,w
                IF_ .Z.         ;if # is pressed
                  movlw 0x2d    ;store - in sign
                  movwf SIGN
                  movf NUML, w  ;adds the NUMH:NUML values to the NUMSTPSH:NUMSTPSL values storing in NUMH:NUML
                  subwf NUMSTPSL, f
                  movf NUMH, w
                  subwfb NUMSTPSH,f
;                  call SendStrg  ;sends values to computer
                  call ResetKeypad  ;clears numh:numl           	        
                ELSE_
                  movf NUMH, w
                  IF_ .NZ.
                    call ResetKeypad  ;resets the keypad
                  ENDIF_
                                ;movff TABLAT, POSTINC0  ;store the tablat value in keystrng
                  movff KEYSTRG+1, KEYSTRG  ;shift digits left, insert new keypress digit
                  movff KEYSTRG+2, KEYSTRG+1
                  movff KEYSTRG+3, KEYSTRG+2
                  movff TABLAT, KEYSTRG+3
                  call ByteOutBuffer  ;keypress detected, update screen
                  movf NUML, W  ;multiply NUML by 10 storing the result in NUMH:NUML
                  mullw 10
                  movff PRODH, NUMH
                  movff PRODL, NUML
                  movlw 0x30    ;subtract 0x30 from tablat value
                  subwf TABLAT, w
                  addwf NUML, f  ;add tablat value to NUMH:NUML
                  movlw 0
                  addwfc NUMH,f
                ENDIF_
              ENDIF_
            ENDIF_
          ENDIF_
        ENDIF_
        return

;;;;;;; ByteOutBuffer subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine updates the display for each new keypress.
;

ByteOutBuffer
        movlw 0xc4              ;sets the Cursor position code into the buffer
        movwf DISP_BUF
        movlw 0x20              ;this portion of the code removes leading space characters replacing them with zeros
        xorwf KEYSTRG,w
        IF_ .Z.
          movlw 0x30
          movwf KEYSTRG
          movlw 0x20
          xorwf KEYSTRG+1,w
          IF_ .Z.
            movlw 0x30
            movwf KEYSTRG+1
            movlw 0x20
            xorwf KEYSTRG+2,w
            IF_ .Z.
              movlw 0x30
              movwf KEYSTRG+2
            ENDIF_
          ENDIF_  
        ENDIF_
        
        movlw 0x30              ;this portion of the code removes leading zero characters replacing them with spaces
        xorwf KEYSTRG,w
        IF_ .Z.
          movlw 0x20
          movwf KEYSTRG
          movlw 0x30
          xorwf KEYSTRG+1,w
          IF_ .Z.
            movlw 0x20
            movwf KEYSTRG+1
            movlw 0x30
            xorwf KEYSTRG+2,w
            IF_ .Z.
              movlw 0x20
              movwf KEYSTRG+2
            ENDIF_
          ENDIF_  
        ENDIF_
        movff KEYSTRG, DISP_BUF+1  ;this portion of the code copies the characters stored in the key buffer into the display buffer
        movff KEYSTRG+1, DISP_BUF+2
        movff KEYSTRG+2, DISP_BUF+3
        movff KEYSTRG+3, DISP_BUF+4
        movlw 0x00              ;terminating the display buffer with a null character
        movwf DISP_BUF+5					
        lfsr 0, DISP_BUF        ;load the address of disp_buf so that the next call will display it
        movf ASCIIENABLED, w	;ensure we are not displaying ASCII characters
        IF_ .Z.
          call DisplayV         ;display the string pointed to by lfsr
        ENDIF_
        return

;;;;;;; StepOutput subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine updates the display for each step taken.
;
StepOutput
        movlw 4             ;Set the display length
        movwf DISPLAYLEN
        lfsr 0,DISP_POS+6   ;Move DISP_POS into the register for buffer formatting
        call LoadBuffer     ;Format the buffer
        movlw 0x81          ;Load the LCD control code
        movwf DISP_POS
        movlw 0x20          ;Set a space at the beginning for as a formatting break
        movwf DISP_POS+1
        IF_ POSH,7 == 1     ;Check for a negative number in the position variable (POS)
          movlw 0x2d        ;If it's negative, load a - sign
        ELSE_
          movf POSH,W       ;Otherwise, we need to check if its either 0 or over 0
          iorwf POSL,W
          IF_ .NZ.
            movlw 0x2b      ;If positive, use a plus sign
          ELSE_
            movlw 0x20      ;otherwise it is 0, load a space
          ENDIF_
        ENDIF_
        movwf DISPLAYSIGN   ;Set the sign into the beginning of the string
        lfsr 0,DISP_POS+5
        movlw 0x20          ;Check for the first space
        xorwf POSTDEC0,w
        REPEAT_
          movlw 0x20        ;Loop through the entire buffer until a space is found
          xorwf POSTDEC0,w
        UNTIL_ .Z.
        movf ASCIIENABLED, w	;ensure we are not displaying ASCII characters
        IF_ .Z.
          iorwf PREINC0, w  ;move back one position to where the space was found
          movff DISPLAYSIGN, POSTINC0  ;replace the space with a sign character ('+' or '-')
          lfsr 0,DISP_POS   ;load the disp_pos buffer address into memory
          call DisplayV     ;display the disp_pos buffer on the LCD
        ENDIF_
        return
        
                
;;;;;;; RPG subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine decyphers RPG changes into values of DELRPG of 0, +1, or -1.
; DELRPG = +1 for CW change, 0 for no change, and -1 for CCW change.

RPG
        clrf  DELRPG            ;Clear for "no change" return value
        movf  PORTD,W           ;Copy PORTD into W
        movwf NEWPORTD          ;and NEWPORTD
        xorwf OLDPORTD,W        ;Any change?
        andlw B'00000011'       ;If not, set Z flag
        IF_  .NZ.               ;If the two bits have changed then
          rrcf  OLDPORTD,W      ; check direction of change by comparing
          xorwf  NEWPORTD,W     ; bit 1 of OLDPORTD with bit 0 of NEWPORTD
          IF_  WREG,0 == 0      ;If same, then change  DELRPG to -1 for CCW
            decf  DELRPG,F
          ELSE_                 ;Otherwise, change DELRPG to  +1 for CW
            incf  DELRPG,F
          ENDIF_
        ENDIF_
        movff NEWPORTD,OLDPORTD  ;Save NEWPORTD as OLDPORTD for ten ms from now
        return
        
;;;;;;; ASCIIDISP subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine sets the RPGVALUE variable and outputs the display to the LCD screen
; as well as outputing to the computer the current RPG value in hex (0 to f)

ASCIIDISP
        movf DELRPG, w          ;add the DELRPG (change in rpg) to the RPGVALUE
        addwf RPGVALUE, F       ;check to make sure it is not negative
        IF_ RPGVALUE,7 == 1	
          incf RPGVALUE, f      ;if negative, set to zero
        ENDIF_
        movlw 240               ;check to make sure RPGVALUE is not >0x0F
        andwf RPGVALUE, w
        IF_ .NZ.
          decf RPGVALUE, f
        ENDIF_
        
        
        movlw 0x0d              ;sends 0x0d to the wreg
        call TXbyte             ;transmits wreg to the Computer
        
        movlw 10				; format the hex value for ascii display on the computer
        subwf RPGVALUE,w
        IF_ .B.					;if it is a number, add 0x30 
          movf RPGVALUE,w
          addlw 0x30			
        ELSE_					;if a letter, add 0x37 (which makes 10 = A, 11=B, etc)
          movf RPGVALUE,w
          addlw 0x37
        ENDIF_
        call TXbyte
        
        
        movlw 0                 ;set the outchar variable to rpg value (high nibble) and 0 (low nibble)
        movwf OUTCHAR
        swapf RPGVALUE, w       ;set the high nibble and put it in the outchar
        iorwf OUTCHAR, f        ;
        
        bcf  PORTE,0            ;Drive RS pin low for cursor positioning code
        movlw  0x80
        call  SendByte          ;Send cursor-positioning code
        bsf  PORTE,0            ;Drive RS pin high for displayable characters
        
        MOVLF  8,BGCOUNT        ;Set BGCOUNT for 8 chars
        
        WHILE_  .NB.            ;While resulting TEMP is 0 or greater,
          movf OUTCHAR, W
          call  SendByte        ;send user-defined character 5 (all segments)
          incf  OUTCHAR, F
          decf  BGCOUNT,F       ;Decrement char counter
        ENDWHILE_
        decf OUTCHAR, F
        bcf  PORTE,0            ;Drive RS pin low for cursor positioning code
        movlw  0xC0
        call  SendByte          ;Send cursor-positioning code
        bsf  PORTE,0            ;Drive RS pin high for displayable characters
        MOVLF  8,BGCOUNT        ;Set BGCOUNT for 8 chars
        WHILE_  .NB.            ;While resulting TEMP is 0 or greater,
          movf OUTCHAR, W
          call  SendByte        ;send user-defined character 5 (all segments)
          incf  OUTCHAR, F
          decf  BGCOUNT,F       ;Decrement char counter
        ENDWHILE_
        
        return

;;;;;;; SendByte subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine sends a single charactor from WREG to the LCD.

SendByte
        bsf  PORTE,1            ;Drive E pin high
        nop                     ;Delay needed if internal clock period = 0.1us
        movwf  PORTD            ;Send upper nibble
        bcf  PORTE,1            ;Drive E pin low so LCD will accept nibble
        bsf  PORTE,1            ;Drive E pin high again
        swapf  WREG,W           ;Swap nibbles
        movwf  PORTD            ;Write lower nibble
        bcf  PORTE,1            ;Drive E pin low so LCD will process byte
        rcall  T40              ;Wait for 40 usec
        return


;;;;;;; ClearDisplay subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine clears the LCD.

ClearDisplay
        bcf  PORTE,0            ;Drive RS pin low for cursor positioning code
        movlw  0x80
        call  SendByte          ;Send cursor-positioning code
        bsf  PORTE,0            ;Drive RS pin high for displayable characters
        MOVLF  8,BGCOUNT        ;Set BGCOUNT for 8 chars
        WHILE_  .NB.            ;While resulting TEMP is 0 or greater,
          movlw  A' '           ;Fill remaining LCD chars with spaces
          call  SendByte        ;Send space to LCD
          decf  BGCOUNT,F       ;Decrement char counter
        ENDWHILE_

        
        bcf  PORTE,0            ;Drive RS pin low for cursor positioning code
        movlw  0xC0
        call  SendByte          ;Send cursor-positioning code
        bsf  PORTE,0            ;Drive RS pin high for displayable characters
        MOVLF  8,BGCOUNT        ;Set BGCOUNT for 8 chars
        WHILE_  .NB.            ;While resulting TEMP is 0 or greater,
          movlw  A' '           ;Fill remaining LCD chars with spaces
          call  SendByte        ;Send space to LCD
          decf  BGCOUNT,F       ;Decrement char counter
        ENDWHILE_

        

        return
        
;;;;;;; LoopTime subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; When Timer2 rolls over and sets the TMR2IF flag, approximately ten
; milliseconds have passed since the last time the flag was set.  The flag
; is cleared and execution returns from the subroutine to the mainline loop.

LoopTime
        REPEAT_
        UNTIL_  PIR1,TMR2IF == 1  ;Wait for completion of ten milliseconds
        bcf  PIR1,TMR2IF        ;Clear flag
        return  

LCDstr  db  0x33,0x32,0x28,0x01,0x0c,0x06,0x00  ;Initialization string for LCD
        
        #include <C:\MATH18\FXD0808U.inc>
        #include <C:\MATH18\FXD1608U.inc>
        #include <KeyCodes.inc>
        end



