MicroChip pic 16c84 16f84 asm code for propeller clock
;---------------------------------------------------------------------------
; A&DI.ASM
; Copyright Don Zehnder all rights reserved December 1997
;
; Code made for "the propeller clock"
; Copyright Bob Blick all rights reserved February 1997
;
;
; This code simulates a analog or digital clock on the propeller
;
; i=improved, reduced analog jitter
;
; Assignments on portb. Say, here's a dumb way to hook things up!
; BIT LED (from center to edge)
; 0 5
; 1 1
; 2 2
; 3 3
; 4 rotation index input
; 5 4
; 6 7
; 7 6
; hours bit 1,2,3,5
; min bit 1,2,3,0,5,7
; seconds bit 6
; time set porta,4
; ~10 MHz crystal
;---------------------------------------------------------------------------
list p=16f84
include "p16f84.inc"
__FUSES _CP_OFF & _WDT_ON & _HS_OSC &
_PWRTE_OFF
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)
ZSEC
;When this rolls over, decrement DMIN
DMIN
;There are 256 DMINs in an hour (1 rev)
ZMIN
;When this rolls over, decrement DHOUR
DHOUR
;There are 256 DHOURs in 12 hours (1 rev)
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
;----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
;----common--------------
COUNTER ;used to slow time
set
PERIOD_CNT ;incremented each 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
endc
#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
#define offset_val d'57'
;offset is n/256 rev for analog.
;needed for proper display position
;since we want analog noon to be where
;the digital colon is. Determined empirically.
#define nths_adj_div2 d'2'
;these are adjusted to correct
#define nths_adj_zsec d'8'
;for observed crystal error in analog mode
#define nths_adj_zmin
d'1' ;time is kept similarly
to AN590
#define in_bit
PORTB,4 ;using
phototransistor connect to ground
#define time_set
PORTA,4 ;Hall-effect,
externally pulled-up
#define prev_flag flags,0
#define mode_bit flags,3
;1=analog, default=digital
org 0x00
goto Start
;-------------------------------
org
0x04
;interrupt vector
Int movwf
safe_w ;save w
swapf
STATUS,w ;swap status, w
movwf
safe_s ;save
status(nibble swap, remember)
test_mode
btfss mode_bit
;test mode, 1=analog
goto digital
;--------------------------------
;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)
subwf DIVISOR1,1
decf DSEC,1
;decrement: reverses direction of display
incfsz ZSEC,1
goto exit
movlw zsec_val
;reset ZSEC when it rolls
movwf ZSEC
movlw nths_adj_zsec
;earlier (minus)
addwf DIVISOR1,1
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
;later (plus)
decf DHOUR,1
exit
;start setting hands on the clock based on time in the
registers
;seconds--------
movlw b'11111111'
;make all outputs high
movwf PORTB
;to turn off all the 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
movf DSEC,W
movwf mulcnd
call mpy_F
;do the math->PERIOD x time
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
;divide result by 256 then compare to PERIOD_CNT
xorwf H_byte,W
btfsc STATUS,Z
sec_on bcf PORTB,6
;minutes-------------------------
movf DMIN,W
movwf mulcnd
call mpy_F
movf H_byte,W
;if H_byte>PERIOD-2 and
subwf TEMP,W
btfsc STATUS,C
goto ngm
movf PERIOD_CNT,1
;if PERIOD_CNT=0 turn on LEDs
btfsc STATUS,Z
goto min_on
ngm movf PERIOD_CNT,W
xorwf H_byte,W
btfss STATUS,Z
;defacto divide by 256 then compare to PERIOD_CNT
goto skipm
min_on bcf PORTB,0
;light up the minute hand
bcf PORTB,5
bcf PORTB,7
bcf PORTB,1
bcf PORTB,2
bcf PORTB,3
skipm
;skipped to here means no turn on
;hours-------------------------
movf DHOUR,W
movwf mulcnd
call mpy_F
movf H_byte,W
;if H_byte>PERIOD-2 and
subwf TEMP,W
;if PERIOD_CNT=0 turn on
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
;defacto divide by 256 then compare to PERIOD_CNT
goto skiph
hour_on bcf PORTB,1
bcf PORTB,2
bcf PORTB,3
bcf PORTB,5
skiph goto end_int
;-----------------------------
;--DIGITAL--------------------
;-----------------------------
digital
; ?????????? 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 0x0c ;corrected
for observed error
movwf bigtick_hi
movlw 0xf8
movwf bigtick_lo
bsf
flags,1 ;notify
Keep_time
incfsz
ADJUST ;make correction
every 256 minutes
goto Bigtick_out
movlw d'243'
subwf
bigtick_lo,f
Bigtick_out
;-------------
;restore and return--
end_int
btfsc
flags,4 ;increment
PERIOD_CNT only every other
goto
clear_flag ;interrupt. Since hand position can
only
incf
PERIOD_CNT,1 ;be determined to the nearest
interrupt,
bsf
flags,4 ;this produces less
hand jitter than using
goto
go_there ;tmr0 prescale of 2x
clear_flag
bcf
flags,4 ;flags,4 is toggled
each interrupt
go_there
clrwdt
bsf
PCLATH,0 ;point to block starting at
0x0100
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.
;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
offset_val ;initialize to start all hands at
12:00
movwf DHOUR
movwf DSEC
movwf DMIN
retlw 0
;-----------------------------------
Port_init movlw
b'00010000' ;all but bit 4 are output
tris
PORTB
;portb 4 is index sensor
movlw b'00010000' ;porta 4 is
timeset
tris
PORTA
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'01011000' ;set up
timer. no prescale
;portb pullups enabled
option
;send w to option. generate warning.
clrf
TMR0
;start timer
retlw 0
;------------------------------------
org 100 ;all
code from which interrupts occur is
;within the
256 word page starting here. PCLATH
;forced to
point here after every interrupt
;---------------------------------------
;Check_index- common to both modes
; Bob Blick's original code also
works. This method
; stabilizes period faster.
;--------------------------------------
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
clrf
digit_index ;set to first digit
clrf dot_index
;first column
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 ;new value much
larger than calc
retlw 0
Calc_neg addlw 2
;allowable deviation = 3
btfss
STATUS,C ;carry will skip
decf
PERIOD ;no carry means it
must be change
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 offset_val
;reset seconds hand to 12:00 position
movwf DSEC
;to make it pretty
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
; ignore bit 4. set=LED off, clear=LED on
;-flipped digits upside down-pattern modified for my crazy LED wiring
Char_tbl
addwf PCL,f
dt 0x42
,0xAD ,0xAD ,0xAD
,0x42
dt 0xEF
,0xED ,0x0 ,0x6D
,0xEF
dt 0x6C
,0x8D ,0xA5 ,0xA9
,0x6D
dt 0xA3
,0xD ,0xAC ,0xAD
,0xAB
dt 0xE7
,0x0 ,0x67 ,0xE6
,0xC7
dt 0x83
,0xAC ,0xAC ,0xAC
,0x2A
dt 0xE3
,0x8D ,0x8D ,0x4D
,0xC2
dt 0x2F
,0xAE ,0x8F ,0xA1
,0xAF
dt 0x62
,0x8D ,0x8D ,0x8D
,0x62
dt 0x46
,0x8B ,0x8D ,0x8D
,0x6E
dt 0xEF
,0xEF ,0x62 ,0xEF
,0xEF
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
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- 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
call
Char_tbl ;get the dot pattern
for this column
D_lookup_3 movwf
PORTB ;send
it to the LEDs
movf
PERIOD,w ;made delay longer
than original program
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 NOPs to increase delay
nop
;to make really wide display
nop
nop
decfsz tick,f
goto Delay_loop ;w is
not damaged, so Delay can
return
;be recalled without reloading
;--------
; 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 ;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
;--------------------
; 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
bcf
mode_bit
btfsc
time_set ;do analog if magnet present on startup
goto
Digital_circle ;otherwise default to digital
bsf
mode_bit ;if mode bit is set-it's analog time
Analog_circle call
Check_index
call Set_analog
goto
Analog_circle
Digital_circle call Check_index
call Display_now
call Set_digital
call Keep_time
goto Digital_circle
end