;;;;;;; P3 for QwikFlash board ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Use 10 MHz crystal frequency. ; Use Timer0 for ten millisecond looptime. ; Blink "Alive" LED every two and a half seconds. ; Use pushbutton to exercise Screens utility. ; ;;;;;;; Program hierarchy ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Mainline ; Initial ; InitLCD ; LoopTime ; DisplayC ; T40 ; BlinkAlive ; Pbutton ; Screens ; DisplayC ; T40 ; Frequency ; Period ; PWmax ; RateRPG ; ByteDisplay ; DisplayC ; T40 ; DisplayV ; T40 ; 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 errorlevel -314, -315 ;Ignore lfsr messages ;;;;;;; Variables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; cblock 0x000 ;Beginning of Access RAM TMR0LCOPY ;Copy of sixteen-bit Timer0 used by LoopTime TMR0HCOPY INTCONCOPY ;Copy of INTCON for LoopTime subroutine COUNT ;Counter available as local to subroutines TEMP ;Temporary local variable ALIVECNT ;Counter for blinking "Alive" LED BYTE ;Eight-bit byte to be displayed BYTESTR:10 ;Display string for binary version of BYTE OLDPORTD ;Holds previous value of inputs DELRPG ;Generated by RPG RPGCNT ;Used to display RPG changes PBSTATE ;Control/status byte for pushbutton PBCOUNT ;Counter for measuring duration of press SCREEN ;State of LCD subroutine LOOP10 ;Scale of ten loop counter THR ;Threshold value used by Pbutton endc ;;;;;;; Equates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ISC equ 0 ;Initiate screen change for slow press ISA equ 1 ;Initiate secondary action for fast press PDONE equ 2 ;Pushbutton action has been taken OLDPB equ 3 ;Old state of pushbutton NEWPB equ 4 ;New state of pushbutton PBthres equ 30 ;Pushbutton threshold for a long press ;;;;;;; Macro definitions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; MOVLF macro literal,dest movlw literal movwf dest endm POINT macro stringname MOVLF high stringname, TBLPTRH MOVLF low stringname, TBLPTRL endm DISPLAY macro register movff register,BYTE call ByteDisplay endm TESTSCREEN macro literal movf SCREEN,W sublw literal endm DISPLAYONCE macro stringname btfss PBSTATE,ISC bra $+14 MOVLF high stringname, TBLPTRH MOVLF low stringname, TBLPTRL call DisplayC endm ;;;;;;; Vectors ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; org 0x0000 ;Reset vector nop goto Mainline org 0x0008 ;High priority interrupt vector goto $ ;Trap org 0x0018 ;Low priority interrupt vector goto $ ;Trap ;;;;;;; Mainline program ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Mainline rcall Initial ;Initialize everything ;LOOP_ L1 rcall BlinkAlive ;Blink "Alive" LED rcall Pbutton ;Check pushbutton rcall Screens ;Deal with SCREEN state rcall LoopTime ;Make looptime be ten milliseconds ;ENDLOOP_ bra L1 PL1 ;;;;;;; Initial subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This subroutine performs all initializations of variables and registers. Initial MOVLF B'10001110',ADCON1 ;Enable PORTA & PORTE digital I/O pins MOVLF B'11100001',TRISA ;Set I/O for PORTA MOVLF B'11011100',TRISB ;Set I/O for PORTB MOVLF B'11010000',TRISC ;Set I/0 for PORTC MOVLF B'00001111',TRISD ;Set I/O for PORTD MOVLF B'00000000',TRISE ;Set I/O for PORTE MOVLF B'10001000',T0CON ;Set up Timer0 for a looptime of 10 ms MOVLF B'00010000',PORTA ;Turn off all four LEDs driven from PORTA rcall InitLCD ;Initialize LCD movff PORTD,OLDPORTD ;Initialize "old" value clrf RPGCNT ;Clear counter to be displayed MOVLF B'00001000',PBSTATE ;Initialize pushbutton state clrf PBCOUNT ;and pushbutton count POINT StrtStr ;Display startup message rcall DisplayC clrf SCREEN ;Initialize Screen's SCREEN variable clrf THR ;Initialize Pbutton's THR variable 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_ L2 rcall LoopTime ;Call LoopTime 10 times decf COUNT,F ;UNTIL_ .Z. bnz L2 RL2 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_ L3 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. bnz L3 RL3 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_ L4 decf COUNT,F ;UNTIL_ .Z. bnz L4 RL4 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. bnz L5 tblrd+* ;If zero, get next byte ;ENDIF_ L5 ;REPEAT_ L6 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. bnz L6 RL6 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_ L7 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? bnz L7 RL7 return ;;;;;;; BlinkAlive subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This subroutine briefly blinks the LED next to the PIC every two-and-a-half ; seconds. BlinkAlive bsf PORTA,RA4 ;Turn off LED decf ALIVECNT,F ;Decrement loop counter and return if not zero ;IF_ .Z. bnz L8 MOVLF 250,ALIVECNT ;Reinitialize BLNKCNT bcf PORTA,RA4 ;Turn on LED for ten milliseconds every 2.5 sec ;ENDIF_ L8 return ;;;;;;; LoopTime subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This subroutine waits for Timer0 to complete its ten millisecond count ; sequence. It does so by waiting for sixteen-bit Timer0 to roll over. To obtain ; a period of precisely 10000/0.4 = 25000 clock periods, it needs to remove ; 65536-25000 or 40536 counts from the sixteen-bit count sequence. The ; algorithm below first copies Timer0 to RAM, adds "Bignum" to the copy ,and ; then writes the result back to Timer0. It actually needs to add somewhat more ; counts to Timer0 than 40536. The extra number of 12+2 counts added into ; "Bignum" makes the precise correction. Bignum equ 65536-25000+12+2 LoopTime ;REPEAT_ L9 ;UNTIL_ INTCON,TMR0IF == 1 ;Wait until ten milliseconds are up btfss INTCON,TMR0IF bra L9 RL9 movff INTCON,INTCONCOPY ;Disable all interrupts to CPU bcf INTCON,GIEH movff TMR0L,TMR0LCOPY ;Read 16-bit counter at this moment movff TMR0H,TMR0HCOPY movlw low Bignum addwf TMR0LCOPY,F movlw high Bignum addwfc TMR0HCOPY,F movff TMR0HCOPY,TMR0H movff TMR0LCOPY,TMR0L ;Write 16-bit counter at this moment movf INTCONCOPY,W ;Restore GIEH interrupt enable bit andlw B'10000000' iorwf INTCON,F bcf INTCON,TMR0IF ;Clear Timer0 flag return ;;;;;;; ByteDisplay subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Display whatever is in BYTE as a binary number. ByteDisplay POINT BYTE_1 ;Display "BYTE=" rcall DisplayC lfsr 0,BYTESTR+8 ;REPEAT_ L10 clrf WREG rrcf BYTE,F ;Move bit into carry rlcf WREG,F ;and from there into WREG iorlw 0x30 ;Convert to ASCII movwf POSTDEC0 ; and move to string movf FSR0L,W ;Done? sublw low BYTESTR ;UNTIL_ .Z. bnz L10 RL10 lfsr 0,BYTESTR ;Set pointer to display string MOVLF 0xc0,BYTESTR ;Add cursor-positioning code clrf BYTESTR+9 ;and end-of-string terminator rcall DisplayV return ;;;;;;; RateRPG subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This subroutine decyphers RPG changes into values of DELRPG. ; DELRPG = +2 for fast CW change, +1 for slow CW change, 0 for no change, ; -1 for slow CCW change and -2 for fast CCW change. Threshold equ 3 ;Value to distinguish between slow and fast RateRPG bcf PORTA,RA2 ;Turn LED off clrf DELRPG ;Clear for "no change" return value movf PORTD,W ;Copy PORTD into W movwf TEMP ; and TEMP xorwf OLDPORTD,W ;Any change? andlw B'00000011' ;If not, set Z flag ;IF_ .NZ. ;If the two bits have changed then... bz L11 rrcf OLDPORTD,W ;Form what a CCW change would produce ;IF_ .C. ;Make new bit 1 = complement of old bit 0 bnc L12 bcf WREG,1 ;ELSE_ bra L13 L12 bsf WREG,1 ;ENDIF_ L13 xorwf TEMP,W ;Did the RPG actually change to this output? andlw B'00000011' ;IF_ .Z. ;If so, then change DELRPG to -1 for CCW bnz L14 decf DELRPG,F movf THR,F ;IF_ .NZ. bz L15 decf DELRPG,F ;If fast turning, decrement again bsf PORTA,RA2 ;Turn LED on ;ENDIF_ L15 ;ELSE_ ;Otherwise, change DELRPG to +1 for CW bra L16 L14 incf DELRPG,F movf THR,F ;IF_ .NZ. bz L17 incf DELRPG,F ;If fast turning, increment again bsf PORTA,RA2 ;Turn LED on ;ENDIF_ L17 ;ENDIF_ L16 MOVLF Threshold+1,THR ;Reinitialize THR ;ENDIF_ L11 movf THR,F ;Does THR equal zero ;IF_ .NZ. ;If not, then decrement it bz L18 decf THR,F ;ENDIF_ L18 movff TEMP,OLDPORTD ;Save PORTD as OLDPORTD for ten ms from now return ;;;;;;; Pbutton subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This subroutine sorts out long and short pushbutton presses into two outputs: ; ISC=1: Initiate screen change for slow press ; ISA=1: Initiate secondary action for fast press ; PDONE=1 One of the above actions has occurred for this press Pbutton bcf PBSTATE,ISC ;Clear Initiate Screen Change bit (if set) bcf PBSTATE,ISA ;Clear Initiate Secondary Action bit (if set) ;IF_ PORTD,RD3 == 1 ;Copy pushbutton state to NEWPB btfss PORTD,RD3 bra L19 bsf PBSTATE,NEWPB ;ELSE_ bra L20 L19 bcf PBSTATE,NEWPB ;ENDIF_ L20 ;IF_ PBSTATE,OLDPB == 1 ;Look for leading edge (OLDPB=1, NEWPB=0) btfss PBSTATE,OLDPB bra L21 ;IF_ PBSTATE,NEWPB == 0 btfsc PBSTATE,NEWPB bra L22 MOVLF PBthres,PBCOUNT ;Start counter ;ENDIF_ L22 ;ENDIF_ L21 ;IF_ PBSTATE,NEWPB == 0 ;Pushbutton is still pressed btfsc PBSTATE,NEWPB bra L23 movf PBCOUNT,F ;IF_ .Z. ;and counter has passed threshold bnz L24 ;IF_ PBSTATE,PDONE == 0 ;and no action has yet been taken btfsc PBSTATE,PDONE bra L25 bsf PBSTATE,ISC ;Initiate screen change bsf PBSTATE,PDONE ;Done with pulse ;ENDIF_ L25 ;ENDIF_ L24 ;ELSE_ ;Pushbutton has been released bra L26 L23 bcf PBSTATE,PDONE ;so clear PDONE ;ENDIF_ L26 ;IF_ PBSTATE,OLDPB == 0 ;Look for trailing edge (OLDPB=0, NEWPB=1) btfsc PBSTATE,OLDPB bra L27 ;IF_ PBSTATE,NEWPB == 1 btfss PBSTATE,NEWPB bra L28 movf PBCOUNT,F ;IF_ .NZ. ;Fast pulse bz L29 bsf PBSTATE,ISA ;Initiate secondary action ;ENDIF_ L29 bcf PBSTATE,PDONE ;Done with pulse clrf PBCOUNT ;Finish counting ;ENDIF_ L28 ;ENDIF_ L27 movf PBCOUNT,F ;Has counter reached zero? ;IF_ .NZ. ;If not, then decrement it bz L30 decf PBCOUNT,F ;ENDIF_ L30 ;IF_ PBSTATE,NEWPB == 1 ;Copy NEWPB to OLDPB btfss PBSTATE,NEWPB bra L31 bsf PBSTATE,OLDPB ;ELSE_ bra L32 L31 bcf PBSTATE,OLDPB ;ENDIF_ L32 return ;;;;;;; Screens subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; This subroutine uses the ISC bit from the Pbutton subroutine to cycle the ; state of SCREEN and to take action based upon its value. ; Initially SCREEN=0, so that whatever screen is displayed by the Initial ; subroutine is not changed until a PB switch press. Then the screen ; corresponding to SCREEN=1 is displayed. Subsequent PB switch ; presses cycle through SCREEN=2, 3, etc., recycling back to SCREEN=1. Screens ;IF_ PBSTATE,ISC == 1 btfss PBSTATE,ISC bra L33 incf SCREEN,F movlw NumberOfScreens+1 ;Check if past last screen subwf SCREEN,W ;IF_ .Z. ;Cycle back to SCREEN=1 bnz L34 MOVLF 1,SCREEN ;ENDIF_ L34 POINT Clear1 ;Clear the display when switching screens rcall DisplayC POINT Clear2 rcall DisplayC ;ENDIF_ L33 TESTSCREEN 1 ;IF_ .Z. bnz L35 DISPLAYONCE FreqStr rcall Frequency ;Frequency-measuring utility ;ENDIF_ L35 TESTSCREEN 2 ;IF_ .Z. bnz L36 DISPLAYONCE PerStr rcall Period ;Period-measuring utility ;ENDIF_ L36 TESTSCREEN 3 ;IF_ .Z. bnz L37 DISPLAYONCE PWmaxStr rcall PWmax ;Maximum-pulse-width measuring utility ;IF_ PBSTATE,ISA == 1 ;Fast pulse, toggle RA1 btfss PBSTATE,ISA bra L38 btg PORTA,RA1 ;ENDIF_ L38 ;ENDIF_ L37 TESTSCREEN 4 ;IF_ .Z. bnz L39 rcall RateRPG ;Decipher RPG inputs into DELRPG movf DELRPG,W addwf RPGCNT,F ;Increment or decrement RPGCNT from RPG DISPLAY RPGCNT ; and display as a binary number ;IF_ PBSTATE,ISA == 1 ;Fast pulse, reset RPGCNT btfss PBSTATE,ISA bra L40 clrf RPGCNT ;ENDIF_ L40 ;ENDIF_ L39 return NumberOfScreens equ 4 ;Change this value if new screens are added ;;;;;;; Stubs for measurement subroutines ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Frequency return Period return PWmax return ;;;;;;; Constant strings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; LCDstr db 0x33,0x32,0x28,0x01,0x0c,0x06,0x00 ;Initialization string for LCD BYTE_1 db "\x80BYTE= \x00" ;Write "BYTE=" to first line of LCD StrtStr db "\x80Push PB \x00" ;Startup screen Clear1 db "\x80 \x00" ;Clear line 1 Clear2 db "\xc0 \x00" ;Clear line 2 FreqStr db "\x80Freq kHz\x00" ;Frequency instrument PerStr db "\x80Per us\x00" ;Period instrument PWmaxStr db "\x80PWmax us\x00" ;Maximum pulse width instrument end