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