;;;;;;; P8 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.
; Blink "Alive" LED every one seconds.
; LCD is initalized and top line set to Freq  Hz
; CCP2CON is set to trigger a high priority interrupt when clocked on pin CCP2/RC1
; Low priority interupts occur when an overflow in tmr3 occurs
; Every second the measurement is restarted; then .4 seconds of measurement are taken
; and displayed on the LCD with a decimal point in the correct position
; 
;;;;;;; Program hierarchy ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Mainline
;   Initial
;     InitLCD
;       LoopTime
;     DisplayC
;       T40
;   BlinkAlive
;     StartMeasureFreq
;   CalculateHz
;     FLO2432S
;     FPD32
;     FLO2432U
;     FPM32
;     Normalize
;       FPM32
;       INT3232
;     DecimalDisplay
;       FXD2408U
;       DisplayV
;         T40
;   LoopTime
;
; HiPriISR
;
; LoPriISR
;
;;;;;;; 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
        ALIVECNT                ;Counter for blinking "Alive" LED
        MX                      ;Highest byte of counter for frequency measurement
        MH                      ;High byte of counter for frequency measurement
        ML                      ;Low byte of counter for frequency measurement
        NX                      ;Highest byte of start edge to stop edge time difference
        NH                      ;High byte of start edge to stop edge time difference
        NL                      ;Low byte of start edge to stop edge time difference
        AXP						;Exponential value of variable A
        AX                      ;Highest byte of generic variable A
        AH                      ;High byte of generic variable A
        AL                      ;Low byte of generic variable A
        STARTX                  ;Highest byte of start time
        STARTH                  ;High byte of start time
        STARTL                  ;Low byte of start time
        TMR3X                   ;Highest byte of timer3
        CCPR2X					;Highest byte of CCPR2
        FIRSTMEAS               ;is this the first measurement
        BYTESTR:10              ;10 word byte buffer to hold the hex
        DIGITCOUNT              ;digit count for the display
        COUNT                   ;used by LCD routines
        TMP0                    ;holds location of decimal from normalize
        STATUS_TEMP             ;temporary variable to hold status during interrupt
        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  HiPriISR          ;Trap

        org  0x0018             ;Low priority interrupt vector
        goto  LowPriISR         ;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  BlinkAlive      ;Blink "Alive" LED
          IF_ PIE2,CCP2IE == 0  ;if CCP2 is not enabled We use the if in the mainline cause we can
            movf FIRSTMEAS,F    ;and if CCP2 was enabled last cycle through
            IF_ .Z.
              movlw 1           ;set FIRSTMEAS indicating last cycle ran through
              movwf FIRSTMEAS
              call CalculateHz  ;calculate the frequency, in Hz
            ENDIF_
          ENDIF_
          bcf PORTC, RC2        ;Toggle pin to support measuring useful function time
          call  LoopTime        ;PWM blue LED while waiting for ten milliseconds
          
        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'11010010',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
        MOVLF  B'00000101',CCP2CON  ; capture rising edge of input
        MOVLF  B'10001001',T3CON
                                ;bsf  PORTB,RB5             ;Step direction is CW
        bcf IPR2, TMR3IP        ;assign low priority to TMR3 overflow interupt   
        bcf PIR2, TMR3IF        ;clear flag for overflow interupt
        bsf PIE2, TMR3IE        ;enable timer3 overflow interupts
        bsf IPR2, CCP2IP        ;assign high priority to TMR3 overflow interupt   
        bcf PIR2, CCP2IF        ;clear flag for overflow interupt
        bsf RCON, IPEN          ;enable high.low interupt structure
        bsf INTCON, GIEL        ;enable low priority interupts to cpu
        bsf INTCON, GIEH        ;enable high priority interupts to cpu
        MOVLF  B'10110111',T2CON  ;Set up Timer2 (10 millisecond looptime)
        MOVLF  222, PR2
        MOVLF  200, ALIVECNT
        call  InitLCD           ;Initialize AFTER setting up LoopTime and ports
        POINT FirstLine         ;output Freq  Hz to the top line of the LCD
        call DisplayC 
        return

;;;;;;; HiPriISR subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine is triggered for the high priority interrupt

HiPriISR
        movf FIRSTMEAS, f       ;check for a 1 in first measurement
        IF_ .NZ.                ;if it is a one
          MOVLF 0, FIRSTMEAS
          movff CCPR2X,STARTX    ;copy the current TMR3X:H:L values into startX:H:L
          movff CCPR2H,STARTH
          movff CCPR2L,STARTL
        ELSE_                   ;if it is a 0
          incf ML,F             ;increment MX:H:L, with carry
          IF_ .C.
            incf MH,F		
            IF_ .C.
              incf MX,F
            ENDIF_
          ENDIF_
        ENDIF_

        IF_ CCPR2X,6 == 1        ;if approximatly 1 million ticks have occured
          bcf PIE2, CCP2IE      ;disable interrupts from CCP2 input
          movff CCPR2L,NL       ;copy CCPR2L:H to NL:H and TMR3x to NX
          movff CCPR2H,NH  
          movff CCPR2X,NX
          movf STARTL, w        ;subtract STARTX:H:L from  NX:H:L
          subwf NL, f		
          movf STARTH,w
          subwfb NH, F
          movf STARTX,w
          subwfb NX, f
        ;ELSE_
          
        ENDIF_
        bcf PIR2, CCP2IF      ;clear interrupt flag for ccp2if
        IF_ PIR2,TMR3IF == 1  ;check to see if low priority interrupt was triggered
          movf CCPR2H,F
          IF_ .Z.
            incf TMR3X,F      ;increase TMR3X if necessary
            bcf PIR2,TMR3IF
          ENDIF_
        ENDIF_
        movff TMR3X,CCPR2X    ;copy TMR3X to CCPR2X
        
        retfie FAST             ;return from function
        
;;;;;;; LowPriISR subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine is triggered for the low priority interrupt

LowPriISR
		bcf INTCON,GIEH
        movff STATUS, STATUS_TEMP  ;store status into temporary variable
        incf TMR3X,F            ;increment TMR3X
        bcf PIR2,TMR3IF         ;clear the timer3 flag
        movff STATUS_TEMP, STATUS  ;restore status
        bsf INTCON,GIEH
        retfie                  ;and 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
        
        
        
;;;;;;; CalculateHz subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine calculates the frequency and displays it on the LCD.

CalculateHz
        movff NX,AARGB0         ;move NX:NH:NL into AARGB0:AARGB1:AARGB2
        movff NH,AARGB1
        movff NL,AARGB2
        call FLO2432U           ;convert NX:NH:NL into a floating point number
        
;         movff AEXP,BEXP         ;place the floating point representation of N into BARGB0:1:2,BEXP
;         movff AARGB0,BARGB0
;         movff AARGB1,BARGB1
;         movff AARGB2,BARGB2
		movff AEXP,AXP         ;place the floating point representation of N into AX:AH:AL,AXP
        movff AARGB0,AX
        movff AARGB1,AH
        movff AARGB2,AL
        
        movff MX,AARGB0         ;convert MX:MH:ML into a floating point number
        movff MH,AARGB1
        movff ML,AARGB2
        call FLO2432U           ;convert MX:MH:ML into a floating point number
        
        movff AXP,BEXP          ;move AX:AH:AL,AXP into BARGB0:1:2,EXP
        movff AX,BARGB0
        movff AH,BARGB1
        movff AL,BARGB2
        
        call FPD32              ;divide floating point M/N, store result into AARGB
        
        movff AEXP,AXP         ;place the floating point representation of M/N into AX:AH:AL,AXP
        movff AARGB0,AX
        movff AARGB1,AH
        movff AARGB2,AL
        
        MOVLF upper 2500000,AARGB0  ;move 2500000 into AARGB0:1:2
        MOVLF high 2500000,AARGB1
        MOVLF low 2500000,AARGB2
        call FLO2432U           ; calculate 2500000 in floating point
        
        movff AXP,BEXP          ;move AX:AH:AL,AXP into BARGB0:1:2,EXP
        movff AX,BARGB0
        movff AH,BARGB1
        movff AL,BARGB2
        
        call FPM32              ;multiply 2500000 by M/N in floating point
        call Normalize          ;normalize the value held by 2500000*M/N
        call DecimalDisplay     ;display this on the LCD
        return
        

;;;;;;; DecimalDisplay subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine initializes the bytestr buffer 
; converts the number returned from normalize into ascii placing the result into 
;    the bytestr buffer while inserting the decimal point into the string and calling
; displayv

DecimalDisplay
        lfsr 0, BYTESTR+10      ;load bytestr into the memory
        MOVLF 0x00, POSTDEC0    ;terminate bytestr buffer
        MOVLF 7, DIGITCOUNT     ;put 7 into digit count so we know when we are in position to put the decimal in
        REPEAT_
          
          movf TMP0,w           ;check to see if this is where the decimal should be
          xorwf DIGITCOUNT, w
          IF_ .Z.               ;if so enter decimal
            MOVLF 0x2e, POSTDEC0
          ENDIF_
          MOVLF 10, BARGB0      ;get the current digit 
          call FXD2408U
          movf REMB0, W
          
          iorlw 0x30            ;convert to ascii
          movwf POSTDEC0        ;load into bytestr buffer
                                ;movf DIGITCOUNT, f			;are we at the beginning of the number being converted to ascii
          decf DIGITCOUNT,f     ;decrement digitcounter 
        UNTIL_ .Z.
        MOVLF 0xc0, INDF0       ;load cursor position code
        call DisplayV           ;display lower line
        return		

        
        
;;;;;;; StartMeasureFreq subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine initializes the frequency measurement bariables and sets up the timer

StartMeasureFreq
        MOVLF 1, FIRSTMEAS      ;indicates this is the first measurement
        MOVLF   0x2f, TMR3X        ;clear the TMR information
        MOVLF   0x2f, CCPR2X
        clrf	TMR3H
        clrf	TMR3L
        bcf		PIR2,CCP2IF        ;clear the interrupt flag for CCP2
        clrf	MX                 ;clear the MX:H:L information
        clrf	MH
        clrf	ML
        bsf		PIE2,CCP2IE        ;re-enable the CCP2 interrupt
        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
;          call StartMeasureFreq  ;initate measurement
        ENDIF_
         movlw 100				;test every second
         xorwf ALIVECNT,W			
         IF_ .Z.
           call StartMeasureFreq  ;initate measurement
         ENDIF_
        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  

;;;;;;; Normalize subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;; Normalize subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine normalizes AARG to a floating point representation of its
; decimal equivalent with the decimal point moved to the right of a seven-digit
; number (i.e., a decimal number between 1,000,000 and 9,999,999).  It is
; assumed that the decimal equivalent of the actual number is less than
; 9,999,999 and more than 0.1000000.

; The location of the actual decimal point is returned in TMP0 with TMP0=0
; representing a number with no digits to the left of the decimal point.
; For example, if AARG is initially the floating point representation of
; 12.34567, then this subroutine will return with AARG holding the floating
; point representation of 1234567.X and with TMP0 = 2.

Normalize
        MOVLF  high PowTen, TBLPTRH  ;Point to first table entry
        MOVLF  low PowTen, TBLPTRL
        MOVLF  8,TMP0           ;Initialize decimal point position
        REPEAT_
          decf  TMP0,F          ;Decrement decimal point position
          IF_ .B.               ;If it borrows, then we've gone too far
            BREAK_              ; so exit the loop
          ENDIF_
          
          TBLRD*+
          movff  TABLAT,BEXP    ;First move table entry to BARG
          TBLRD*+
          movff  TABLAT,BARGB0
          TBLRD*+
          movff  TABLAT,BARGB1
          TBLRD*+
          movff  TABLAT,BARGB2
          movf  AARGB2,W        ;Subtract AARG from power of ten
          subwf  BARGB2,W
          movf  AARGB1,W
          subwfb  BARGB1,W
          movf  AARGB0,W
          subwfb  BARGB0,W
          movf  AEXP,W
          subwfb  BEXP,W
        UNTIL_  .NC.            ;Continue until AARG is larger than power of ten
        
        MOVLF  high PowTen, TBLPTRH  ;Form TBLPTR = PowTen + 4(TMP0)
        MOVLF  low PowTen, TBLPTRL
        decf  TMP0,W            ;Use the decimal point position to select
        bcf  STATUS,C           ;the power of ten needed
        rlcf  WREG,F
        rlcf  WREG,F
        addwf  TBLPTRL,F        ;And point to it in the table
        clrf  WREG
        addwfc  TBLPTRH,F
        
        TBLRD*+                 ;Now retrieve this power of ten to BARG
        movff  TABLAT,BEXP
        TBLRD*+
        movff  TABLAT,BARGB0
        TBLRD*+
        movff  TABLAT,BARGB1
        TBLRD*+
        movff  TABLAT,BARGB2
        call  FPM32             ;Multiply AARG by this power of ten
                                ; to get a value greater than 1,000,000
        
;;;;;;; Need to convert to a four-byte integer because the largest signed three-
;;;;;;; byte number has a decimal value of 8388607
        call  INT3232
        movff  AARGB1,AARGB0    ;Keep the lower three bytes
        movff  AARGB2,AARGB1
        movff  AARGB3,AARGB2

        return
        
        
LCDstr  db  0x33,0x32,0x28,0x01,0x0c,0x06,0x00  ;Initialization string for LCD

FirstLine db "\x80Freq  Hz\x00"  ;Freq  Hz for first line display        

PowTen  db  0x92,0x74,0x24,0x00  ;Floating point representation of 1000000
        db  0x8F,0x43,0x50,0x00  ;100000
        db  0x8C,0x1C,0x40,0x00  ;10000
        db  0x88,0x7A,0x00,0x00  ;1000
        db  0x85,0x48,0x00,0x00  ;100
        db  0x82,0x20,0x00,0x00  ;10
        db  0x7F,0x00,0x00,0x00  ;1
        db  0x7b,0x4c,0xcc,0xcd  ;0.1
        
        #include <C:\MATH18\FPSUBS.inc>
        #include <C:\MATH18\FXD2408U.inc>
        end


