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. Until the display shows more than 9 hours the increments are 0.1 ; hours (6 minutes). At 10 hours the decimal point disappears and only hours are displayed up to 99. If the timer is ; inactive it displays a single dot. ; Version 1.1: Fixed ghosting ; Version 1.2: Added clear FET after reset (safety - Fire Alarm interface) ; 10 September 2011 ; GPL Copyleft 2011 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 MSBHRS EQU 0x28 ; Divide-by-10 result of hours to display in MSB #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.5 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 0x0B ; Preload TIMER1 MOVWF TMR1H MOVLW 0xDB 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 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) MOVLW 0xFF ; Pull Port B high to eliminate ghosting MOVWF PORTB ; 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 less than 10 MOVLW 0x0A SUBWF HOURS,0 ; if true, C=0, must display TENTHS BTFSC STATUS,C GOTO LSBhours ; Carry is set ; 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 LSBhours MOVFW HOURS ; FUCK! Needs to strip hours first! MOVWF TEMPHRS MOVLW 0x0A Striprepeat SUBWF TEMPHRS,1 BTFSC STATUS,C GOTO Striprepeat ADDWF TEMPHRS,0 ; Since once too many restore stripped hours CALL bin2seg MOVWF PORTB RETFIE LSBLAMPTST MOVLW 0x00 MOVWF 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 ; Check if hours less than 10 MOVLW 0x0A SUBWF HOURS,0 ; if true, C=0, must display hours BTFSS STATUS,C GOTO MSBhours ; Carry is clear ; Greater than 9, need to display hours*10 MOVFW HOURS ; Not again! Needs to strip hours first! MOVWF TEMPHRS MOVLW 0x0A CLRF MSBHRS Striprepeat10 INCF MSBHRS,1 ; Increase MSBHRS then subtract 10 SUBWF TEMPHRS,1 BTFSC STATUS,C GOTO Striprepeat10 DECF MSBHRS,1 ; Compensate for extra addition MOVFW MSBHRS ; CALL bin2seg MOVWF PORTB RETFIE MSBhours MOVFW HOURS CALL bin2seg MOVWF PORTB BCF PORTB,7 ; Turn on decimal point RETFIE MSBLAMPTST MOVLW 0x00 MOVWF PORTB RETFIE ; END Interrupthandlers Main ;Init stuff 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 BCF FET ; Switch Lock Off 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 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 ; Check if HOURS more than 10 (disregard TENTHS) MOVLW 0x0A SUBWF HOURS,0 ; if true, C=0, HOURS not yet 10 BTFSC STATUS,C GOTO IncHours ; Carry is set INCF TENTHS,1 ; Check if more than 10 MOVLW 0x0A SUBWF TENTHS,0 ; if true, C=0, TENTHS not yet 10 BTFSC STATUS,C GOTO IncHours ; Carry is set BCF UPDB RETURN IncHours CLRF TENTHS INCF HOURS,1 ; Check if hours >99 MOVLW 0x63 SUBWF HOURS,0 ; if true, C=0, HOURS not yet 99 BTFSS STATUS,C GOTO HoursOK MOVLW 0x63 MOVWF HOURS 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