title "maglocktimer.asm" ; This is a custom timer. It uses a dual 7-segment common anode LED display and drives a logic level FET to switch ; on a magnetic lock once the UP button is pressed. The increments are 0.1 hours (6 minutes). At 9.9 hours no more ; time is added. If the timer is inactive it displays a single dot. ; v1.0 120319 ; v1.1 180125 Fixed late port clear after reset ; v1.2 180827 Fixed accuracy (TIMER1 from 0x0BDB to 0x0FC2) ; ; GPL Copyleft 2012 pic@polonai.se LIST P=16F628A, F=INHX8M #include <p16f628a.inc> __CONFIG _INTRC_OSC_NOCLKOUT & _WDT_ON & _LVP_OFF & _CP_OFF ; Equates RESET_V EQU 0x00 ; Address of RESET Vector OSC_FREQ EQU D'4000000' ; Oscillator Frequency is 4 MHz ; Registers GPFLAGS EQU 0x20 ; General Purpose Flags #define LEDTOGL GPFLAGS,0 ; toggles LED1 and 2 (0: LSB=LED2, 1: MSB=LED1) #define DP1 GPFLAGS,1 ; Decimal Point Leftmost (MSB) LED (not used) #define DP2 GPFLAGS,2 ; DP2 (only for lamp test) (not used) #define UPDB GPFLAGS,3 ; UP Switch Debounced #define UPTR GPFLAGS,4 ; UP Switch Triggered #define UPRP GPFLAGS,5 ; UP Switch Repeat #define LAMPTST GPFLAGS,7 ; Lamp Test HOURS EQU 0x21 ; Hours TENTHS EQU 0x22 ; Tenth Hours (6 minutes) MINUTES EQU 0x23 ; Minutes (only 6 of them) SECONDS EQU 0x24 ; Actually, this is 0.5 seconds :-/ DEBOUNCE EQU 0x25 ; Debounce Counter for "UP" switch (every 4ms) BTNRPT EQU 0x26 ; Repeat Counter for "UP" switch (not used) TEMPHRS EQU 0x27 ; LSB display of hours when >= 10 hours (not used) MSBHRS EQU 0x28 ; Divide-by-10 result of hours to display in MSB (not used) #define FET PORTA,2 ; Switch maglock FET on #define UPSW PORTA,4 ; "UP" switch #define LED1 PORTA,1 ; MSB Anode (via PNP like BC557B) #define LED2 PORTA,0 ; LSB Anode org 0x00 GOTO Main org 0x04 ; Interrupt handler code goes here ; NOTE: Since virtually every part of this program runs inside an interrupt handler, it's not necesssary ; to back up any critical registers when entering or exiting the handler. BTFSC PIR1,TMR1IF ; Check to see if the interrupt was caused by a TIMER1 rollover Overflow) GOTO Timehandler ; 0.25 second timer BTFSC PIR1,TMR2IF ; Check to see if the interrupt was caused by a TIMER2 rollover (overflow) GOTO Multiplex ; 16 ms multiplex time for LED display RETFIE ; If none of the above is true, then just leave the interrupt handler Timehandler BCF PIR1,TMR1IF ; Clear Interrupt Source MOVLW 0x0F ; Preload TIMER1 MOVWF TMR1H MOVLW 0xC2 MOVWF TMR1L BCF LAMPTST ; No need to have those after the first half second BTFSS FET ; If the FET is not on we should keep our hands off the time counters RETFIE ; Check "seconds" DECFSZ SECONDS,1 RETFIE MOVLW 0x78 ; Reload seconds (120) MOVWF SECONDS ; Check "minutes" DECFSZ MINUTES,1 RETFIE MOVLW 0x06 ; Reload "minutes" MOVWF MINUTES ; Check if TENTHS is zero MOVLW 0x00 SUBWF TENTHS,0 ; if true, Z=1, exit BTFSC STATUS,Z GOTO CHKHRS ; Not zero DECF TENTHS,1 RETFIE CHKHRS MOVLW 0x09 ; Reload TENTHS MOVWF TENTHS ; Check if HOURS is zero MOVLW 0x00 SUBWF HOURS,0 ; if true, Z=1, exit BTFSC STATUS,Z RETFIE ; Zero DECF HOURS,1 RETFIE ; The above could be simplified by not using the value zero in TENTHS and HOURS and then compensate in the 7-segment lookup. Oh well... Multiplex BCF PIR1,TMR2IF ; Clear Interrupt source CLRWDT ; Clear Watchdog Timer (this runs every 4.0 ms, so with 18 ms WDT time-out that should be OK) ; Check UP switch first BTFSS UPSW ; Read UP switch ("clear" is pressed) CALL ButtonPressed BTFSC UPTR ; Check if debounce in progress CALL ButtonDebounce BTFSC UPDB ; Handle Button Event CALL ButtonHandler ; Toggle LEDs MOVLW B'00000001' XORWF GPFLAGS,1 ; Toggle LEDs BTFSS LEDTOGL ; set means LSB display GOTO MSBLED BSF LED1 BCF LED2 ; LSB LED, check if LAMPTST BTFSC LAMPTST GOTO LSBLAMPTST ; LSB LED, check if HOURS is zero (must display blank and a dot) MOVLW 0x00 SUBWF HOURS,0 ; if true, Z=1, must display a dot only BTFSS STATUS,Z GOTO LSBtenths ; LSB LED, check if TENTHS is also zero CLRF W SUBWF TENTHS,0 BTFSS STATUS,Z GOTO LSBtenths MOVLW 0x7F ; Display a dot only MOVWF PORTB BCF FET ; Turn off FET RETFIE LSBtenths MOVFW TENTHS CALL bin2seg MOVWF PORTB RETFIE LSBLAMPTST CLRF PORTB RETFIE MSBLED ; MSB LED, check if LAMPTST BSF LED2 BCF LED1 BTFSC LAMPTST GOTO MSBLAMPTST ; Check if hours is zero, must display blank MOVLW 0x00 SUBWF HOURS,0 ; if true, Z=1, must display a dot only BTFSS STATUS,Z GOTO MSBnotzero ; Check is tenths is also zero CLRF W SUBWF TENTHS,0 BTFSS STATUS,Z GOTO MSBnotzero MOVLW 0xFF ; Display blank MOVWF PORTB RETFIE MSBnotzero MOVFW HOURS CALL bin2seg MOVWF PORTB BCF PORTB,7 ; Turn on decimal point RETFIE MSBLAMPTST CLRF PORTB RETFIE ; END Interrupthandlers Main ;Init stuff BCF FET ; Make sure FET is off after reset BCF STATUS,RP0 ; Go to bank 0 BCF STATUS,RP1 ; Go to bank 0 MOVLW 0x07 ; Turn comparators off MOVWF CMCON BSF STATUS,RP0 ; Go to bank 1 MOVLW B'11111000' ; PORTA,0-2 Outputs MOVWF TRISA CLRF TRISB ; Set port B as all outputs BSF OPTION_REG,PSA ; Something with Watchdog Timer BCF STATUS,RP0 ; Go back to bank 0 BSF T1CON,T1CKPS1 ; These set the TIMER1 prescaler. Here are the possible values: BSF T1CON,T1CKPS0 ; 00=1:1 01=1:2 10=1:4 11=1:8 BCF T1CON,T1OSCEN ; Turn off the TIMER1 oscillator to save power (we don't need it because we're using the internal oscillator) BCF T1CON,TMR1CS ; Select the internal oscillator for TIMER1 BSF T1CON,TMR1ON ; Enable TIMER1 BSF T2CON,T2CKPS1 ; Prescale 1:16 for TIMER2 BCF T2CON,T2CKPS0 ; BCF T2CON,TOUTPS3 ; Postscale 1:1 for TIMER2 BCF T2CON,TOUTPS2 ; BCF T2CON,TOUTPS1 ; BCF T2CON,TOUTPS0 ; BSF T2CON,TMR2ON ; Enable TIMER2 ; Enable interrupts BCF INTCON,INTF ; Clear INTF flag before enabling interrupts BCF INTCON,T0IF ; Clear T0IF (TIMER0 Interrupt Flag) before enabling interrupts BCF PIR1,TMR1IF ; Clear TMR1IF (TIMER1 Interrupt Flag) before enabling interrupts BCF PIR1,TMR2IF ; Clear TMR2IF (TIMER1 Interrupt Flag) before enabling interrupts BCF INTCON,INTE ; Clear interrupt on RB0/INT pin (an External interrupt) BSF INTCON,PEIE ; Enable PEIE (PEripheral Interrupt Enable - for TIMER1, the 16 bit timer) BSF STATUS,RP0 ; Go to bank 1 BSF PIE1,TMR1IE ; Enable interrupt on TIMER1 overflow (when the TMR1 register pair wraps around from 0xFFFF to 0x0000) BSF PIE1,TMR2IE ; Enable interrupt on TIMER2 overflow BCF PIE1,CMIE ; Disable interrupt on comparator output change BCF STATUS,RP0 ; Go to bank 0 BSF INTCON,GIE ; Enable global interrupts ; INIT Registers CLRF GPFLAGS BSF LAMPTST ; Lamp Test during the first second CLRF TMR1H CLRF TMR1L CLRF HOURS CLRF TENTHS MOVLW 0x06 MOVWF MINUTES ; Preload the MINUTES counter (6 minutes is 1 TENTHS is 0.1 HOURS) MOVLW 0x78 ; 0x78=120 half-seconds MOVWF SECONDS MOVLW 0x32 ; 200ms Debounce Time MOVWF DEBOUNCE WaitForInterrupt GOTO WaitForInterrupt ; Do Nothing ; Subroutines ; All button related routines are called from Multiplex ButtonPressed ; This will introduce 200ms debounce delay BSF UPTR RETURN ButtonDebounce ; When UPTR is true ; This could have been done more elegantly but hey, it works DECFSZ DEBOUNCE,1 RETURN BSF UPDB MOVLW 0x32 MOVWF DEBOUNCE BCF UPTR RETURN ButtonHandler ; Handles Button Events (duh!) BSF FET ; Turn on FET MOVLW 0x06 MOVWF MINUTES ; Preload the MINUTES counter to avoid partial counts for TENTHS MOVLW 0x78 ; 0x78=120 half-seconds MOVWF SECONDS ; Same for SECONDS INCF TENTHS,1 MOVLW 0x0A ; Check if more than 10 SUBWF TENTHS,0 BTFSC STATUS,C ; if true, C=0, TENTHS not yet 10 GOTO IncHours ; Carry is set BCF UPDB RETURN IncHours CLRF TENTHS INCF HOURS,1 ; Check if hours >9 MOVLW 0x0A SUBWF HOURS,0 ; if true, C=0, HOURS not yet 10 BTFSS STATUS,C GOTO HoursOK MOVLW 0x09 MOVWF HOURS MOVWF TENTHS HoursOK BCF UPDB RETURN bin2seg ADDWF PCL, F ; Jump into the lookup table RETLW 0xC0 ; Return segment code for 0 RETLW 0xF9 ; Return segment code for 1 RETLW 0xA4 ; Return segment code for 2 RETLW 0xB0 ; Return segment code for 3 RETLW 0x99 ; Return segment code for 4 RETLW 0x92 ; Return segment code for 5 RETLW 0x82 ; Return segment code for 6 RETLW 0xF8 ; Return segment code for 7 RETLW 0x80 ; Return segment code for 8 RETLW 0x90 ; Return segment code for 9 RETLW 0xFF ; Return segment code for blank RETLW 0x7F ; Decimal Point Only END