;******************************************************************************
;
; BINCLOCK.asm
; Practical Binary Digit Clock - built to implement the
; "Black Standard" for human interface binary digit clocks.
;
; Open source - anyone can use as they choose.
; Note! See text: www.RomanBlack.com/binclock.htm
;
; Note! Uses the (no ints) zero-error clock routine as seen;
; www.romanblack.com/one_sec.htm
;
; Ver 1.1 - started 14 March 2007 - Roman Black
; ver 1.2 - 15 June 2009 - Roman Black, with thanks to tester Art Loya 
;  (v1.2 fixed bugs and improved clock setting speed.)
;******************************************************************************


;==============================================================================
; MPLAB stuff here

	ERRORLEVEL -224		; suppress annoying message because of option/tris
	ERRORLEVEL -302		; suppress message because of bank select in setup ports

	LIST b=5, n=97, t=ON, st=OFF
	; absolute listing tabs=5, lines=97, trim long lines=ON, symbol table=OFF

	; config for 16F628
	__CONFIG   _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC & _MCLRE_ON & _BODEN_ON & _LVP_OFF
	; oscillator is XT ready for 4 MHz crystal or resonator.
	; (use _HS_OSC (not _XT_OSC)if you are using 8MHz or higher.)
;==============================================================================
; processor defined ;
	include <p16f628.inc>

;==============================================================================
; Variables here

	CBLOCK 0x20			; start of ram in 16F628

		bres_hi			; hi byte of our 24bit variable
		bres_mid			; mid byte
		bres_lo			; lo byte (all used for Bres)
	
		sec				; 3 vars, binary clock data
		min				;
		hour				;

		buttons			; holds input buttons bit6 bit7
		debounce			; counter to time debounce
	ENDC


	; My custom instructions!
	#define	incw		addlw d'1'		; add 1 to w
	#define	comw		xorlw 0xFF		; complement w
	#define	skpwne	skpnz			; after subxx, uses zero
	#define	skpweq	skpz				; after subxx, uses zero
	#define	skpwle	skpc				; after subxx, uses carry
	#define	skpwgt	skpnc			; after subxx, uses carry
	#define	skp1		goto $+1+1		; skips next 1 instruction.
	#define	skp2		goto $+1+2		; skips next 2 instructions.
	#define	skp3		goto $+1+3		; skips next 3 instructions.
	#define	back1	goto $-1			; loops back 1 instruction.
	#define	nop2		goto $+1			; = 2 nops (wait)


;==============================================================================
; constants for the Bresenham 24bit 1second timer algorithm (in hex)

	#define BHI 		0x0F		; msb value (change these to suit xtal)
	#define BMID 		0x42		; mid value 
	#define BLO 		0x40		; lsb value 

	; You can use ANY xtal frequency! some samples below;
	;  32.768kHz =     8,192 = 00 20 00
	;  4MHz xtal = 1,000,000 = 0F 42 40 *using
	;  6MHz xtal = 1,500,000 = 16 E3 60
	;  8MHz xtal = 2,000,000 = 1E 84 80
	; 16MHz xtal = 4,000,000 = 3D 09 00
	; 20MHz xtal = 5,000,000 = 4C 4B 40

;==============================================================================
; Code here

	org 0x000 			; Set program memory base at reset vector 0x000
reset
	goto setup			; set up ints and port stuff
						; Note! No interrupts are used in this code.
;==============================================================================



;******************************************************************************
;  SETUP     (runs this only once at startup)
;******************************************************************************
;
;------------------
setup					; goto label
;------------------

	;-------------------------------------------------
	; Note! 16F628 version.
	; Note! here we set up peripherals and port directions.
	; this will need to be changed for different PICs.
	;-------------------------------------------------
						; OPTION setup
	movlw b'00001000'		;
		;  x-------		; 7, 0=enable, 1=disable, portb pullups
		;  -x------		; 6, 1=/, int edge select bit
		;  --x-----		; 5, timer0 source, 0=internal clock
		;  ---x----		; 4, timer0 ext edge, 1=\
		;  ----x---		; 3, prescaler assign, 1=wdt, 0=timer0
		;  -----x--		; 2,1,0, timer0 prescaler rate select
		;  ------x-		;   000=2, 001=4, 010=8, 011=16, etc.
		;  -------x		; 
						; Note! We set timer0 prescaler to 1:1,
						; this is explained later in MAIN.
						;
	banksel OPTION_REG		; 
	movwf OPTION_REG		; load data into OPTION_REG
	banksel 0				; 
	;-------------------------------------------------
	; extra setup for 16F628
	banksel VRCON			; do bank 1 stuff
	clrf PIE1				; disable pie etc
	clrf VRCON			; disable Vref
	banksel 0				;

	clrf T2CON			; disable timer2
	clrf CCP1CON			; disable CCP module

	movlw b'00000111'		; disable comparators
	movwf CMCON			;
	;-------------------------------------------------
						; TIMER1 setup
						;  prescale 1:1, timer OFF.
	movlw b'00000000'		; 
		;  --x-----		; 5,4; TMR1 prescale; 11=8
		;  ---x----		;  10=4, 01=2, 00=1
		;  ----x---		; T1OSCEN; 0=osc off
		;  -----x--		; T1SYNC; 0=external clock sync
		;  ------x-		; TMR1CS; clock source, 1=ext, 0=int
		;  -------x		; TMR1ON; 1=on, 0=off

	movwf T1CON			;
	;-------------------------------------------------
						; PORTB pins direction setup
						; 1=input, 0=output
	clrf PORTB			;
						;
	movlw b'11000000'		; 6,7 are inputs
						;
	banksel TRISB			; 
	movwf TRISB			; send mask to portb
	banksel 0				; 
	;-------------------------------------------------
						; PORTA pins direction setup
						; 1=input, 0=output
	clrf PORTA			;
						;
	movlw b'00000000'		; all 5 porta are outputs,
						; (with 16F628 porta only has lower 5 bits)
						;
	banksel TRISB			; 
	movwf TRISA			; send mask to porta
	banksel 0				; 
	;-------------------------------------------------
						; INTCON setup
						;
						; for this code example, all we need to do is
						; make sure all interrupts are off.
						;
	movlw b'00000000'		; GIE=off TOIE=off (timer0 overflow int)
	movwf INTCON			; set up ints.

	;-------------------------------------------------
	; Now set up the variables
	;-------------------------------------------------

	clrf bres_hi			; set up the 24bit bres variable 
	clrf bres_lo			; ready to roll straight away...
	clrf bres_mid			; 
	incf bres_mid,f		; mid MUST be >0 at start!!

	clrf sec				; setup the 3 clock vars
	clrf min				;
	movlw d'4'			; 4= 1oclock
	movwf hour			;

	clrf PORTA			; safe, set all leds OFF to start
	clrf PORTB			;

	;-------------------------------------------------
	goto main				; start main program

;------------------------------------------------------------------------------



;******************************************************************************
;  MAIN     (main program loop)
;******************************************************************************
;
;------------------
main						; goto label
;------------------

	;-------------------------------------------------
	; Note! This example does not use interrupts.
	; This can very handy, if using smaller PICs like the 12c508,
	; or in designs where interrupts would be a problem
	; but where you still need a reliable period event.
	;
	; However we must poll the "fake" interrupt and manually
	; check timer0. This system is quite fast, which does not
	; use up too much timeslice.
	;
	; The "fake" interrupt is done by polling timer0 int flag.
	; This is set after 256 instructions, and stays
	; set for a safe period of 256 instructions, during this last
	; 256 instructions we MUST poll and detect it.
	;
	; So the MAX delay between polls is 256 to 512 instructions.
	; Suggest safe value of 250 instructions between polls.
	;
	; Make sure your main program code never makes more than
	; 250 instructions before the next polling of the "fake" int.
	;-------------------------------------------------
main_loop					; 
						; Note! here you have your main program code,
						; or calls to the main program pieces.
						; Remember, nothing should take longer than
						; 256 instructions total or we might miss a
						; "fake" interrupt.

	;-------------------------------------------------
	; Poll (check) for our "fake" int here!
	;-------------------------------------------------
						; all we do is check if TMR0 int flag is set,
						; if so we generate a "fake" interrupt.
						;
	btfss INTCON,T0IF		; test if overflow flag is set
	goto main_loop			; clr, no fake int, keep looping.
						; set, we have reached 256 counts since last
						; "fake" interrupt.

	;-------------------------------------------------
	; If it gets here we have detected a "fake" interrupt!
	;-------------------------------------------------
	; Note! This will occur every 256 instructions, we
	; can now do our special one second timing system.				

	; This consists of three main steps;
	; * subtract 256 counts from our 24bit variable
	; * test if we reached the setpoint
	; * if so, add 1,000,000 counts to 24bit variable and generate event.
	;-------------------------------------------------
						; first clear timer0 int flag
						; this never resets timer0 so it stays accurate,
						; as it still contains the lower bits and will keep
						; perfect time.
	bcf INTCON,T0IF		;

	;-------------------------------------------------
						; * optimised 24 bit subtract here 
						; This is done with the minimum instructions.
						; We subtract 256 from the 24bit variable
						; by just decrementing the mid byte.

	tstf bres_mid			; first test for mid==0
	skpnz				; nz = no underflow needed
	decf bres_hi,f			; z, so is underflow, so dec the msb

	decfsz bres_mid,f		; dec the mid byte (subtract 256)

						; now the full 24bit optimised subtract is done!
						; this is almost 4 times faster than a "proper"
						; 24bit subtract.

	goto main_loop			; nz, so definitely not one second yet.
						; in most cases the entire 'fake" int takes
						; only 9 instructions.
	;------------------------
						; * test if we have reached one second.
						; only gets here when mid==0, it MAY be one second.
						; only gets to here 1 in every 256 times.
						; (this is our best optimised test)
						; it gets here when bres_mid ==0.

	call change_time		; test buttons every 65536 timer0 ticks
						; with 4MHz xtal that is 15Hz

	tstf bres_hi			; test hi for zero too
	skpz					; z = both hi and mid are zero, is one second!
	goto main_loop			; nz, so not one second yet.

	;-------------------------------------------------
	; Only gets to here if we have reached one second.
	; now we can generate our one second event,
	; the other thing we need to do is add X counts
	; to our 24bit variable and start all over again.
	;-------------------------------------------------
						; Add the X counts first.

						; As we know hi==0 and mid==0 this makes it very fast.
						; This is an optimised 24bit add, because we can
						; just load the top two bytes and only need to do
						; a real add on the bottom byte. This is much quicker
						; than a "proper" 24bit add.

	movlw BHI				; get msb value 
	movwf bres_hi			; load in msb

	movlw BMID			; get mid value
	movwf bres_mid			; load in mid

	movlw BLO				; lsb value to add
	addwf bres_lo,f		; add it to the remainder already in lsb
	skpnc				; nc = no overflow, so mid is still ok

	incf bres_mid,f		; c, so lsb overflowed, so inc mid
						; this is optimised and relies on mid being known
						; and that mid <FF and won't overflow from one inc.

						; that's it! Our optimised 24bit add is done,
						; this is roughly twice as quick as a "proper"
						; 24bit add.

	;-------------------------------------------------
	; Now add 1 second to the clock.
	; Seconds 	= no display
	; Minutes 	= 4 leds, 0-14 range
	; 1/4 Hours 	= 2 leds, 0-3 range
	; Hours 		= 4 leds, 1-12 range
	;-------------------------------------------------
	; gets here every second

every_sec

	incf sec,f			; add 1 second to clock
	movf sec,w			;
	sublw d'60'			; test if sec=60
	skpweq				; weq, =60
	goto main_end			; wne, <60


	; gets here every minute

every_min

	clrf sec				; reset seconds

	incf min,f			; add 1 minute to clock
	movf min,w			;
	sublw d'15'			; test
	skpweq				; weq, =15
	goto main_end			; wne, <15

	; gets here every 1/4 hour

every_quart

	clrf min				; reset minutes

	; hours and 1/4 hours are all in same variable
	; variable is actually 1/4 hours, 6 bits
	; so 12.0 hours is actually 12x4 or 48.
	; minimum is 1.0 hours (4+0)
	; maximum is 12.45 (48+3) (roll at 52)

	incf hour,f			; add a 1/4hour to clock

	movf hour,w			;
	sublw d'52'			; test
	skpweq				; weq, =52
	goto main_end			; wne, <52

	; gets here every 12 hours

every_twelve

	movlw d'4'			; reset hours and 1/4 hours to 1 oclock
	movwf hour			;



	;-------------------------------------------------
main_end

	; send the new clock "digits" out to the leds

	movf min,w			; minutes
	movwf PORTA			; 

	movf hour,w			; 1/4hours and hours
	movwf PORTB			;

	; all done for this second!
	goto main_loop			;

;------------------------------------------------------------------------------





;******************************************************************************
;  CHANGE_TIME     changes clock "time" if buttons are pressed
;******************************************************************************
;
;------------------
change_time				; routine
;------------------

	;-------------------------------------------------
	; this routine is called every 256x256 timer0 ticks.
	; with 4MHz xtal that is 15Hz
	; Change debounce count if using different xtal!
	; 
	; Holding any buttons down just slowly increases
	; that field. Debounce is number of times it gets
	; here before it incs.
	; Try inc at about 0.33 second?
	; pin LO=button pressed
	;-------------------------------------------------
	; debounce timer, only change time every X times

	incf debounce,f		;
	movf debounce,w		;
	sublw d'5'			; 5 is about 0.33 sec
	skpweq				;
	return				;

	;-------------------------------------------------
	; gets here every X times
	; so change time if buttons are pressed

	clrf debounce			; reset debounce timer

	movf PORTB,w			; read buttons
	movwf buttons			;

	;-------------------------------------------------
	; process hours button

test_hour
	btfsc buttons,6		; bit6=hours
	goto test_min			;

	; need to inc hours and check roll.  4-51

	incf hour,f			; add a 1/4hour to clock
	movf hour,w			;
	sublw d'52'			; test
	skpweq				; weq, =52
	goto show_hour			; wne, <52
	
	movlw d'4'			; reset hours and 1/4 hours to 1 oclock
	movwf hour			;

show_hour
	movf hour,w			; display 1/4hours and hours
	movwf PORTB			;

	;-------------------------------------------------
	; process minutes button

test_min
	btfsc buttons,7		; bit7=mins
	return				;

	; need to inc mins and check roll.  0-14

	incf min,f			; add 1 minute to clock
	movf min,w			;
	sublw d'15'			; test
	skpwne				; wne, <15
	clrf min				; weq, =15

	movf min,w			; display new minutes
	movwf PORTA			; 

	;-------------------------------------------------

	; finally, always reset seconds (to re-start the minute)
	clrf sec				;

	return				;

;------------------------------------------------------------------------------



;==============================================================================
	end					; no code after this point.
;==============================================================================


; NOTES!
; PortA 0-3 = minutes, 4 leds, hi= on
; PortB 0-1 = 1/4 hours, 2 leds, hi = on
; PortB 2-5 = hours, 4 leds, hi = on

; PortB 6 = mode button, active low
; PortB 7 = adjust button, active low








