;;;;;;; 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_ rcall BlinkAlive ;Blink "Alive" LED rcall Pbutton ;Check pushbutton rcall Screens ;Deal with SCREEN state rcall LoopTime ;Make looptime be ten milliseconds ENDLOOP_ ;;;;;;; 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_ 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 ;;;;;;; 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 ;;;;;;;;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 ;;;;;;; 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. MOVLF 250,ALIVECNT ;Reinitialize BLNKCNT bcf PORTA,RA4 ;Turn on LED for ten milliseconds every 2.5 sec ENDIF_ 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_ UNTIL_ INTCON,TMR0IF == 1 ;Wait until ten milliseconds are up 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_ 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. 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... rrcf OLDPORTD,W ;Form what a CCW change would produce IF_ .C. ;Make new bit 1 = complement of old bit 0 bcf WREG,1 ELSE_ bsf WREG,1 ENDIF_ 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 decf DELRPG,F movf THR,F IF_ .NZ. decf DELRPG,F ;If fast turning, decrement again bsf PORTA,RA2 ;Turn LED on ENDIF_ ELSE_ ;Otherwise, change DELRPG to +1 for CW incf DELRPG,F movf THR,F IF_ .NZ. incf DELRPG,F ;If fast turning, increment again bsf PORTA,RA2 ;Turn LED on ENDIF_ ENDIF_ MOVLF Threshold+1,THR ;Reinitialize THR ENDIF_ movf THR,F ;Does THR equal zero IF_ .NZ. ;If not, then decrement it decf THR,F ENDIF_ 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 bsf PBSTATE,NEWPB ELSE_ bcf PBSTATE,NEWPB ENDIF_ IF_ PBSTATE,OLDPB == 1 ;Look for leading edge (OLDPB=1, NEWPB=0) IF_ PBSTATE,NEWPB == 0 MOVLF PBthres,PBCOUNT ;Start counter ENDIF_ ENDIF_ IF_ PBSTATE,NEWPB == 0 ;Pushbutton is still pressed movf PBCOUNT,F IF_ .Z. ;and counter has passed threshold IF_ PBSTATE,PDONE == 0 ;and no action has yet been taken bsf PBSTATE,ISC ;Initiate screen change bsf PBSTATE,PDONE ;Done with pulse ENDIF_ ENDIF_ ELSE_ ;Pushbutton has been released bcf PBSTATE,PDONE ;so clear PDONE ENDIF_ IF_ PBSTATE,OLDPB == 0 ;Look for trailing edge (OLDPB=0, NEWPB=1) IF_ PBSTATE,NEWPB == 1 movf PBCOUNT,F IF_ .NZ. ;Fast pulse bsf PBSTATE,ISA ;Initiate secondary action ENDIF_ bcf PBSTATE,PDONE ;Done with pulse clrf PBCOUNT ;Finish counting ENDIF_ ENDIF_ movf PBCOUNT,F ;Has counter reached zero? IF_ .NZ. ;If not, then decrement it decf PBCOUNT,F ENDIF_ IF_ PBSTATE,NEWPB == 1 ;Copy NEWPB to OLDPB bsf PBSTATE,OLDPB ELSE_ bcf PBSTATE,OLDPB ENDIF_ 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 incf SCREEN,F movlw NumberOfScreens+1 ;Check if past last screen subwf SCREEN,W IF_ .Z. ;Cycle back to SCREEN=1 MOVLF 1,SCREEN ENDIF_ POINT Clear1 ;Clear the display when switching screens rcall DisplayC POINT Clear2 rcall DisplayC ENDIF_ TESTSCREEN 1 IF_ .Z. DISPLAYONCE FreqStr rcall Frequency ;Frequency-measuring utility ENDIF_ TESTSCREEN 2 IF_ .Z. DISPLAYONCE PerStr rcall Period ;Period-measuring utility ENDIF_ TESTSCREEN 3 IF_ .Z. DISPLAYONCE PWmaxStr rcall PWmax ;Maximum-pulse-width measuring utility IF_ PBSTATE,ISA == 1 ;Fast pulse, toggle RA1 btg PORTA,RA1 ENDIF_ ENDIF_ TESTSCREEN 4 IF_ .Z. 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 clrf RPGCNT ENDIF_ ENDIF_ 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