;
;2modbl_1.asm
;3/29/01 many minor changes. Hands in analog mode are wider (stay on longer)
; mode switching somewhat improved
;Switch modes using a Hall Effect sensor
;based on Bob Blick's propellor clock
;
;Analog and demo modes by Don Zehnder
;
;mode 0: digital
;mode 1: analog
;mode 2: switches between analog and digital. Demo mode
;
;Assignments on portb, LEDs logic 0 = on.
;BIT LED (from center=0 to edge=6)
;0 0
;1 1
;2 2
;3 3
;4 4
;5 5
;6 6
;7 unused
;
;Assignments on portA
;0 index external pull-up (normally high)
;1 outermost LED logic 0 = on
;2 time setting external pull-up (normally high)
;3 mode switch external pull-up (normally high)
;4 inner LEDs logic 0 = on, switched w/ a transistor. Better to have put this on any
; other port to avoid need for a biasing resistor
;
;Analog Hands, portb bits:
;hours bit 0,1,2,3 plus portA,4
;min bit 0,1,2,3,4,5 plus portA,4
;seconds bit 7
;10 MHz crystal. Timings given are for 10.00000 Mhz, can adjust for accuracy
;
;Motor runs counterclockwise at >1150 RPM. Index sensor is triggered at the 3 o'clock position.
list p=16f84
include "p16f84.inc"
__FUSES _CP_OFF & _WDT_ON & _HS_OSC & _PWRTE_ON
cblock 0x0c
;----unique to analog---
DIVISOR1 ;these are to count # of interrupts
DIVISOR2 ;required to increment ZSEC, decrement DSEC
DSEC ;There are 256 DSECs in a minute (1 rev of hands)
ZSEC ;When this rolls over, decrement DMIN
DMIN ;There are 256 DMINs in an hour (1 rev of hands)
ZMIN ;When this rolls over, decrement DHOUR
DHOUR ;There are 256 DHOURs in 12 hours (1 rev of hands)
mulcnd ; 8 bit multiplicand
H_byte ; High byte of the 16 bit result
L_byte ; Low byte of the 16 bit result - (unused result)
TEMP ; part of routine for setting hands when period=0
HOLDFLAGS ;flags to determine on/off status of hands
sec_on_cnt ;counters for number of interrupts since hands were turned on
min_on_cnt
hour_on_cnt
;----unique to digital---
dot_index ;which column is being displayed
digit_index ;which digit is being displayed
hours ;in display format, not hex(01-12)
minutes ;00 to 59
bigtick_dbl ;incremented each interrupt
bigtick_hi
bigtick_lo
scratch ;scratch value
tick ;used by delay
COUNTER2 ;used as secondary counter to slow time set
ADJUST ;fine adjust on timing. See time adjustment notes or spreadsheet
;corrects for observed crystal error to get most accurate time
;----common--------------
COUNTER ;used to slow time set
PERIOD_CNT ;incremented every other interrupt
PERIOD ;stable period after hysteresis calc.
safe_w ;copy of W safe from interrupt
safe_s ;copy of STATUS safe from interrupt
period_dup ;copy of period_count safe from interrupt
flags ;b2=int, b1=minute, b0=prev_flag, b3=mode_bit, b4=toggle for period_count
mode ;0=digital, 1=analog, 2=demo
mode_debounce ;counters used to control response to mode switch sensor
mode_debounce1
mode_debounce2 ;controls sensitivity only when displaying analog clock
MODE_TIMER
endc
;----ANALOG-----
#define divisor1_val d'147' ;roll-over at 109 (256-109)=147
#define divisor2_val d'235' ;roll-over at 21
#define zsec_val d'196' ;min hand moves 1/60 fast as sec hand
#define zmin_val d'244' ;hour hand moves 1/12 fast as min hand
;-------------------ADJUST AS REQUIRED FOR ACCURACY-make sure to change the corresponding
;-------------------operation to add or subtract as required-----------------------------
#define nths_adj_div2 d'0' ;these can be adjusted to correct
#define nths_adj_zsec d'11' ;for observed crystal error in analog mode
#define nths_adj_zmin d'1' ;time is kept similarly to AN590
#define sec_flag HOLDFLAGS,0
#define min_flag HOLDFLAGS,1
#define hour_flag HOLDFLAGS,2
#define hand_on_cnt_val d'2' ;how many interrupts to keep each hand lit
;-----COMMON-------
#define in_bit PORTA,0 ;using phototransistor connect to ground
#define time_set PORTA,3 ;Hall-effect, externally pulled-up
#define mode_switch PORTA,2 ;Hall-effect, externally pulled-up
#define prev_flag flags,0 ;whether or not index sense was detected on prev iteration
#define mode_bit flags,3 ;applies to mode 3 (demo) 1=analog, 0=digital
#define debounce_val d'50' ;determines how sensitive the mode switching is
#define debounce_val2 d'20' ;determines how sensitive the mode switching is from analog display
;-----DIGITAL------
#define OVERHEAD_VAL d'35' ;overhead that is subtracted to make delay work out right
org 0x00
goto Start
;----------------------------------------------
;---INTERRUPT ROUTINE--------------------------
org 0x04 ;interrupt vector
Int movwf safe_w ;save w
swapf STATUS,w ;swap status, w
movwf safe_s ;save status(nibble swap, remember)
;--------------------------------------
; Keep digital time--------------------
; 585937.5 interrupts every minute. Increment the bigtick each time.
incf bigtick_lo,f
btfsc STATUS,Z
incf bigtick_hi,f
btfsc STATUS,Z
incfsz bigtick_dbl,f
goto Bigtick_out
;--------
; here? bigtick has rolled over to zero and one minute has passed.
; reload bigtick and set a flag for the main counter
;--------
movlw 0xf7 ;these timings for no prescale
movwf bigtick_dbl ;and 10Mhz crystal
movlw 0x0f ;not corrected for observed error
movwf bigtick_hi
movlw 0x2f
movwf bigtick_lo
bsf flags,1 ;notify Keep_time
incfsz ADJUST,1 ;make correction every 256 minutes
goto Bigtick_out
movlw d'128' ;****adjust for accuracy here
addwf bigtick_lo,f ;****add or subtact as required
Bigtick_out
;--------------------------------
;analog main time keeping code
;--------------------------------
incfsz DIVISOR1,1
goto exit
movlw divisor1_val ;reset DIVISOR1 every time it rolls over
movwf DIVISOR1
incfsz DIVISOR2,1
goto exit
movlw divisor2_val ;reset DIVISOR2 each time it rolls
movwf DIVISOR2
movlw nths_adj_div2 ;adj-roll later (plus sign in time calculation spreadsheet)
subwf DIVISOR1,1 ;****MANUALLY CHANGE THIS TO ADD OR SUBTRACT AS REQUIRED
decf DSEC,1 ;decrement: reverses direction of display. get here 256 times/minute
decfsz MODE_TIMER,1 ;toggle the mode_bit. Determines analog or digital when in demo mode
goto no_mode
movlw d'25' ;about 6 seconds
movwf MODE_TIMER ;reload it
movlw b'00001000'
xorwf flags,1 ;mode flag (bit 3) . toggle it.
no_mode incfsz ZSEC,1
goto exit
movlw zsec_val ;reset ZSEC when it rolls
movwf ZSEC
movlw nths_adj_zsec ;earlier (minus)
addwf DIVISOR1,1 ;****zsec, MANUALLY CHANGE THIS TO ADD OR SUBTRACT AS REQUIRED
decf DMIN,1
incfsz ZMIN,1
goto exit
movlw zmin_val ;reset ZMIN when it rolls
movwf ZMIN
movlw nths_adj_zmin
subwf DIVISOR1,1 ;****zmin, MANUALLY CHANGE THIS TO ADD OR SUBTRACT AS REQUIRED
decf DHOUR,1
exit
;---------NOW take action depending on mode it is in-----------
test_mode
movf mode,f
btfsc STATUS,Z
goto i_digital ;if mode=0
movlw d'1'
xorwf mode,w
btfsc STATUS,Z
goto i_analog ;if mode = 1
btfss mode_bit ;mode=2, (demo)
goto i_digital ;if mode=2 and bit clear, do digital
goto i_analog ;else analog
;-Analog mode--------------
i_analog
movlw b'11111111' ;make all outputs high
movwf PORTB ;to turn off all the LEDs
bsf PORTA,4 ;turn off the inner LEDs
movf PERIOD,W ;some junk is here to fix problem
movwf TEMP ;where hands turn off when PERIOD_CNT is 0
movlw d'2'
subwf TEMP,1 ;TEMP=PERIOD-2
;-start setting hands on the clock based on time in the registers
;seconds--------
movf DSEC,W
movwf mulcnd
call mpy_F ;do the math->PERIOD x time/256 = H_byte
movf H_byte,W ;if H_byte>PERIOD-2 and
subwf TEMP,W
btfsc STATUS,C
goto ng
movf PERIOD_CNT,1 ;if PERIOD_CNT=0 turn on LED
btfsc STATUS,Z
goto sec_on
ng movf PERIOD_CNT,W ;compare result of the calculation to PERIOD_CNT
xorwf H_byte,W
btfss STATUS,Z ;turn on LED if PERIOD_CNT matches calculated value
goto not_sec_on
sec_on bcf PORTB,6
bsf sec_flag ;set the flag that controls whether or not hand will light on next interrupt
clrf sec_on_cnt
goto i_min
not_sec_on
btfss sec_flag ;no math match, but turn on if it it has not timed out yet
goto i_min
bcf PORTB,6 ;on
incf sec_on_cnt,1
movlw hand_on_cnt_val
xorwf sec_on_cnt,W
btfsc STATUS,Z
bcf sec_flag ;clear flag so it won't go on at next interrupt
;minutes--------
i_min movf DMIN,W ;most everything is the same as for seconds
movwf mulcnd
call mpy_F
movf H_byte,W
subwf TEMP,W
btfsc STATUS,C
goto ngm
movf PERIOD_CNT,1
btfsc STATUS,Z
goto min_on
ngm movf PERIOD_CNT,W
xorwf H_byte,W
btfss STATUS,Z
goto not_min_on
min_on bcf PORTA,4 ;turn inner LEDs on
movlw b'11000000' ;this is the pattern for this hand
andwf PORTB,1 ;turn it on
bsf min_flag
clrf min_on_cnt
goto i_hour
not_min_on
btfss min_flag
goto i_hour
bcf PORTA,4
movlw b'11000000' ;this is the pattern for this hand
andwf PORTB,1 ;turn it on
incf min_on_cnt,1
movlw hand_on_cnt_val
xorwf min_on_cnt,W
btfsc STATUS,Z
bcf min_flag
;hours-------------------------
i_hour movf DHOUR,W ;everything is the same as for minutes
movwf mulcnd
call mpy_F
movf H_byte,W
subwf TEMP,W
btfsc STATUS,C
goto ngh
movf PERIOD_CNT,1
btfsc STATUS,Z
goto hour_on
ngh movf PERIOD_CNT,W
xorwf H_byte,W
btfss STATUS,Z
goto not_hour_on
hour_on bcf PORTA,4
movlw b'11110000'
andwf PORTB,1
bsf hour_flag
clrf hour_on_cnt
goto end_int
not_hour_on
btfss hour_flag
goto end_int
bcf PORTA,4
movlw b'11110000'
andwf PORTB,1
incf hour_on_cnt,1
movlw hand_on_cnt_val
xorwf hour_on_cnt,W
btfsc STATUS,Z
bcf hour_flag
;-----digital------
i_digital ;don't need to do anything
;------------------
end_int btfsc flags,4 ;increment PERIOD_CNT only every other
incf PERIOD_CNT,1 ;interrupt. Since hand position can only
;be determined to the nearest interrupt,
;this produces less hand jitter than using
;tmr0 prescale of 2x (otherwise period_cnt will overflow at 10Mhz
;and this RPM)
movlw b'00010000'
xorwf flags,1 ;toggle bit 4 of flags
;restore and return--
go_there clrwdt
swapf safe_s,w ;fetch status, reswap nibbles
movwf STATUS ;restore status
swapf safe_w,f ;swap nibbles in preparation
swapf safe_w,w ;for the swap restoration of w
bcf INTCON,2 ;clear interrupt flag before return
retfie ;return from interrupt
;*******************************************************************
;Time, whether sec, min, or hour is kept as a value between 0 to 255,
;and is maintained in DSEC, DMIN and DHOUR
;To scale the time in order to compare to the current value of PERIOD_CNT,
;multiply time*PERIOD and divide by 256, which is just the high
;byte of time*PERIOD result
; ( Fast Version : Straight Line Code )
; Before calling the subroutine " mpy_F ", the multiplier is PERIOD
; multiplicand is " mulcnd " . The 16 bit result is stored in locations
; H_byte & L_byte.
;*******************************************************************
;**** Define a macro for adding & right shifting **
mult MACRO bit ; Begin macro
btfsc PERIOD,bit
addwf H_byte,1
rrf H_byte,1
rrf L_byte,1
ENDM ; End of macro
; ***************************** Begin Multiplier Routine
mpy_F clrf H_byte
clrf L_byte
movf mulcnd,W ; move the multiplicand to W reg.
bcf STATUS,C ; Clear the carry bit in the status Reg.
mult 0
mult 1
mult 2
mult 3
mult 4
mult 5
mult 6
mult 7
retlw 0
;--initialize all RAM-digital and analog---------------------
;
Ram_init ;***digital*****
movlw 0x12 ;why do clocks always start
movwf hours ;at 12:00 ?
clrf minutes
clrf dot_index
clrf digit_index
movlw 0xFB
movwf bigtick_dbl
movlw d'5'
movwf COUNTER2
;****analog*******
movlw divisor1_val ;initialize DIVISOR1
movwf DIVISOR1
movlw divisor2_val ;initialize DIVISOR2
movwf DIVISOR2
movlw zsec_val ;and other timekeeping
movwf ZSEC ;counters
movlw zmin_val
movwf ZMIN
movlw d'63' ;1/4 of a revolution of hands
movwf DHOUR ;ensures that hands start at 12:00 position
movwf DSEC
movwf DMIN
;****common*****
clrf mode
movlw debounce_val ;load debouce counters
movwf mode_debounce
movlw debounce_val2
movwf mode_debounce2
retlw 0
;-----------------------------------
Port_init movlw b'00000000' ;all are output
tris PORTB
movlw b'00001101' ;porta 0 is index sensor
tris PORTA ;porta 2 is timeset, porta 3 is mode switch
retlw 0
;-----------------------------------
Timer_init bcf INTCON,2 ;clear TMR0 int flag
bsf INTCON,7 ;enable global interrupts
bsf INTCON,5 ;enable TMR0 int
clrf TMR0 ;clear timer
clrwdt ;why is this needed? just do it..
movlw b'00011000' ;set up timer. no prescale
;portb pullups not enabled
option ;send w to option. generate warning.
clrf TMR0 ;start timer
retlw 0
;------------------------------------
org 100
;---------------------------------------
;Check_index- common to all modes
;
;--------------------------------------
Check_index btfss in_bit
goto no_phase
btfsc prev_flag
goto cont
bsf prev_flag
movf PERIOD_CNT,w ;make a working copy
movwf period_dup ;called period dup
clrf PERIOD_CNT ;a fresh start for next rotation
movf mode,f
btfsc STATUS,Z
goto digex ;if mode=0
movlw d'1'
xorwf mode,w
btfsc STATUS,Z
goto not_digital ;if mode = 1
btfss mode_bit ;mode=2, (demo)
goto digex ;if mode=2 and bit clear, do digital
goto not_digital
digex ;don't need to do the delay if in analog mode
clrf digit_index ;set to first digit
clrf dot_index ;first column
movlw OVERHEAD_VAL ;For digital. do one delay 1st. To get colon at the 12:00 position
;we make delay=1/64 of a revolution. Since display is
;30 columns wide, putting one delay 1st ensures that
;the colon is always centered at 12:00
subwf PERIOD,W ;W from F, put in W
call Delay ;width of digits with this delay
not_digital
bcf flags,4 ;clear period count toggle bit. PERIOD_CNT
goto cont ;will be incremented on the next interrupt
no_phase bcf prev_flag
; calculate a period that does not dither or jitter
; period will not be changed unless new period is really different
cont movf PERIOD,W
subwf period_dup,W ;find difference
btfss STATUS,C ;carry flag set means no borrow
goto Calc_neg ;must be other way
sublw 2 ;allowable deviation = 3
btfss STATUS,C ;borrow won't skip
incf PERIOD,1 ;new value much larger than calc
retlw 0
Calc_neg addlw 2 ;allowable deviation = 3
btfss STATUS,C ;carry will skip
decf PERIOD,1 ;no carry means it must be change
;cont movf period_dup,W
; movwf PERIOD
retlw 0
;-------------------
;set_analog for adjusting time
;-------------------
Set_analog
btfsc time_set ;input uses pull-up- normally set
retlw 0 ;exit if no magnet near
decfsz COUNTER,1 ;COUNTER used to take action only every 256 loops
retlw 0 ;this slows down time setting
movlw d'63' ;reset seconds hand to 12:00 position
movwf DSEC ;
decf DMIN,1 ;adjust time one minute
incfsz ZMIN,1 ;this one goes the other way
retlw 0 ;if ZMIN rolls, reset it and decrement DHOUR
movlw zmin_val
movwf ZMIN
decf DHOUR,1
retlw 0
;****************************************
;---Digital Code--subs below here
;****************************************
;--------
; CHARACTER LOOKUP TABLE
; set=LED off, clear=LED on
;-flipped digits upside down and mirrored
Char_tbl
addwf PCL,f
dt 0x41 ,0x3E ,0x3E ,0x3E, 0x41
dt 0x7F ,0x7E ,0x0 ,0x5E, 0x7F
dt 0x4E ,0x36 ,0x3A ,0x3C, 0x5E
dt 0x39 ,0x16 ,0x2E ,0x3E, 0x3D
dt 0x7B ,0x0 ,0x5B ,0x6B, 0x73
dt 0x31 ,0x2E ,0x2E ,0x2E, 0xD
dt 0x79 ,0x36 ,0x36 ,0x56, 0x61
dt 0x1F ,0x2F ,0x37 ,0x38, 0x3F
dt 0x49 ,0x36 ,0x36 ,0x36, 0x49
dt 0x43 ,0x35 ,0x36 ,0x36, 0x4F
dt 0x7F ,0x7F ,0x49 ,0x49, 0x7F
Char_tbl_end
;--------
; change LED pattern based on state of digit_index and dot_index
;--------
Display_now movlw 0x05
xorwf dot_index,w ;test for end of digit
movlw 0xFF ;pattern for blank column
btfsc STATUS,Z
goto D_lookup_3 ;it needs a blank
bcf STATUS,C ;clear carry before a rotate
rlf digit_index,w ;double the index because each
bcf PCLATH,1
bsf PCLATH,0
addwf PCL,f ;takes two instructions
D_1min movf minutes,w ;DZ-reversed order of display
goto D_lookup ;since my motor is counterclockwise
D_10min swapf minutes,w
goto D_lookup
D_colon movlw 0x0A
goto D_lookup
D_1hr movf hours,w
goto D_lookup
D_10hr swapf hours,w
goto D_lookup
D_nothing retlw 0
D_lookup andlw b'00001111' ;strip off hi bits
movwf scratch ;multiply this by 5 for lookup
addwf scratch,f ;table base position
addwf scratch,f ;is this cheating?
addwf scratch,f ;I think not.
addwf scratch,f ;I think it is conserving energy!
btfss STATUS,Z ;test for zero
goto D_lookup_2 ;not a zero
movlw 0x04 ;-DZ changes for counterclockwise- 10 hours is now digit 5
xorwf digit_index,w ;-if digit 5 is 0, display blank
movlw 0xFF ;this makes a blank LED pattern
btfsc STATUS,Z ;test if it is 10 hrs digit
goto D_lookup_3 ;it's a trailing zero
D_lookup_2 movf dot_index,w ;get column
addwf scratch,w ;add it to digit base
bcf PCLATH,1 ;set to page 0x100 for lookup
bsf PCLATH,0
call Char_tbl ;get the dot pattern for this column
D_lookup_3 movwf PORTB ;send it to the LEDs
movlw OVERHEAD_VAL
subwf PERIOD,W ;W from F, put in W
call Delay ;width of digits with this delay
incf dot_index,f ;increment to the next column
movlw 0x06 ;6 columns is a digit plus space
xorwf dot_index,w ;next digit test
btfss STATUS,Z
retlw 0 ;not a new digit
clrf dot_index ;new digit time
incf digit_index,f
retlw 0 ;Display_now done.
;--------
; a short delay routine
;--------
Delay movwf tick
Delay_loop nop ;added 4 NOPs to increase delay
nop ;so delay is 8 cycles * tick, or 1/64 of a revolution
nop ;w is not damaged, so Delay can
nop ;be recalled without reloading
nop
decfsz tick,f
goto Delay_loop
return
;--------
; test for magnet and adjust time if needed
;--------
Set_digital btfsc time_set ;input uses pull-up- normally set
retlw 0 ;exit if no magnet near
decfsz COUNTER,1 ;COUNTER used to take action only every 256 loops
retlw 0 ;this slows down time setting
decfsz COUNTER2,1 ;COUNTER2 slows things even more
retlw 0
movlw d'8' ;adjust to suit for setting speed
movwf COUNTER2 ;reset COUNTER2 when it rolls
call Inc_mins ;got to here, now adjust time
retlw 0
;--------
; increment one hour
;--------
Inc_hours movlw 0x12
xorwf hours,w
btfsc STATUS,Z
goto Inc_hours_12
movlw 0x07 ;this part gets a little sloppy
addwf hours,w
movlw 0x07
btfss STATUS,DC
movlw 1
addwf hours,f
retlw 0
Inc_hours_12 movlw 0x01
movwf hours
retlw 0
;--------
; increment the time based on flags,1 as sent by interrupt routine
; Inc_mins also used by time setting routine
;--------
Keep_time btfss flags,1 ;the minutes flag
retlw 0 ;not this time
bcf flags,1 ;clear the minutes flag
Inc_mins movlw 0x07 ;start incrementing time
addwf minutes,w ;add 7 minutes into w
btfsc STATUS,DC ;did adding 7 cause digit carry?
goto Sixty_mins ;then test for an hour change
incf minutes,1 ;otherwise add 1 for real
retlw 0 ;and go back
Sixty_mins movwf minutes ;save the minutes
movlw 0x60 ;test for 60
xorwf minutes,w ;are minutes at 60?
btfss STATUS,Z
retlw 0 ;no? go back
clrf minutes ;otherwise zero minutes
goto Inc_hours ;and increment hours
;--------------------
; check_mode reads status of the mode switching sensor to increment the mode
;--------------------
check_mode btfsc mode_switch
retlw 0
movlw d'1' ;if we are in mode 1(analog), we need more loops
xorwf mode,w ;to make mode switch less sensitive since we get here more
btfsc STATUS,Z
goto mode1
goto not_mode1
mode1 decfsz mode_debounce2,1
retlw 0
movlw d'20'
movwf mode_debounce2
not_mode1
decfsz mode_debounce1,1 ; used to take action only every 256 loops
retlw 0 ; this slows down mode switching
decfsz mode_debounce,1 ; slows things even more
retlw 0
incf mode,1 ;increment_mode
movlw d'3' ;resetting to 0 if not 0, 1 or 2
xorwf mode,w
btfsc STATUS,Z
clrf mode
movlw debounce_val
movwf mode_debounce
retlw 0
;--------------------
; Program starts here
;--------------------
Start call Ram_init ;set variables to nice values
call Port_init ;set port directions
call Timer_init ;start timer based interrupt
;--------------
circle call Keep_time ;for digital only, but needs to keep running regardless
call check_mode
movf mode,f
btfsc STATUS,Z
goto digital ;if mode=0
movlw d'1'
xorwf mode,w
btfsc STATUS,Z
goto analog ;if mode = 1, else mode=2
btfss mode_bit
goto digital ;case mode=2 and bit clear, do digital
goto analog ;else digital
analog bcf PORTA,1 ;turn on the outer LED for ring effect
call Check_index
call Set_analog
goto circle
digital bsf PORTA,1 ;turn OFF the outer LED (no ring effect)
bsf PORTA,4 ;turn OFF the inner LEDs
call Check_index
call Display_now
call Set_digital
goto circle
end