;******************************************************************************
; ZERO-ERROR ONE SECOND TIMER
; (Roman Black 2001, public domain, use it as you like)
; 
; NO INTERRUPTS VERSION
;
; for PIC 16F84 at 4 MHz (or most PICs)
; (this code has been assembled with MPLAB and hardware tested)
; (for best viewing set TABS=5 in MPLAB editor)
;
; Note! See text: www.RomanBlack.com/one_sec.htm
;
; Generates an event every second (or other period) from any PIC
; with any clock frequency.
;
; This version uses a "fake" interrupt at every 256 instructions.
; Code can be adapted for different clock speeds, period lengths
; and accuracy levels.
;
; May 2004 Errata! Bug fixed in main() where timer0 bit7 was cleared.
; June 2009 update! Changed the TMR0 code to allow more PIC versions
;                 with PICs that have no interrupts.
;******************************************************************************


;==============================================================================
; processor defined ;
	include <p16f84.inc>

;==============================================================================
; MPLAB stuff here

	LIST b=5, n=97, t=ON, st=OFF
	; absolute listing tabs=5, lines=97, trim long lines=ON, symbol table=OFF

	__CONFIG   _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC
	; config for 16F84; code protect OFF, watchdog OFF, powerup timer ON,
	; oscillator is XT ready for 4 MHz crystal or resonator.

;==============================================================================
; Variables here

	CBLOCK 0x20			; start of ram in 16F84

		bres_hi			; hi byte of our 24bit variable
		bres_mid			; mid byte
		bres_lo			; lo byte

		bit7flag			; used to test TMR0
						; (we only need 4 bytes for this system)
	ENDC


;==============================================================================
; 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! 16F84 version.
	; Note! here we set up peripherals and port directions.
	; this will need to be changed for different PICs.
	;-------------------------------------------------
						; OPTION setup
	movlw b'10000000'		;
		;  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:2,
						; this is explained later in MAIN.
						;
	banksel OPTION_REG		; go proper reg bank
	movwf OPTION_REG		; load data into OPTION_REG
	banksel 0				; back to normal bank 0
	;-------------------------------------------------
						; PORTB pins direction setup
						; 1=input, 0=output
	clrf PORTB			;
						;
	movlw b'00000000'		; all 8 portb are outputs
						;
	banksel TRISB			; go proper reg bank
	movwf TRISB			; send mask to portb
	banksel 0				; back to normal reg bank
	;-------------------------------------------------
						; PORTA pins direction setup
						; 1=input, 0=output
	clrf PORTA			;
						;
	movlw b'00000000'		; all 5 porta are outputs,
						; (with 16F84 porta only has lower 5 bits)
						;
	banksel TRISB			; go proper reg bank
	movwf TRISA			; send mask to porta
	banksel 0				; back to normal reg bank
	;-------------------------------------------------
						; 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.

	;-------------------------------------------------
	; Note! Now the hardware is set up we need to load the
	; first count for one second into our 24bit bres variable.
	;-------------------------------------------------
						; Note! This example uses 4 MHz clock, which is
						; 1,000,000 counts per second.
						;
						; We require a 1 second period, so we must load
						; 1,000,000 counts each time.
						; 1,000,000 = 0F 42 40 (in hex)
						;
						; We also need to add 256 counts for the first time,
						; so we just add 1 to the mid byte.
						; Check mid overflow if needed.

						; here we load the 24bit variable.
	movlw 0x0F			; get msb value 
	movwf bres_hi			; put in hi

	movlw 0x42 +1			; get mid value (note we added 1 to it)
	movwf bres_mid			; put in mid

	movlw 0x40			; get lsb value
	movwf bres_lo			; put in mid

						; now setup is complete, we can start execution.
	;-------------------------------------------------
	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 bit7 of timer0,
	; which is prescaled at 1:2. 
	; This means bit7 changes after 256 instructions, and stays
	; for a safe period of 256 instructions, during this last
	; 256 instructions we MUST poll and detect the next change.
	;
	; So the MAX delay between polls is 256 to 512 instructions.
	;-------------------------------------------------
main_loop					; 
						; Note! here you have your main program code,
						; or calls to the main program pieces.
						; Remember, nothing should take longer than
						; 250 instructions total or we might miss a
						; "fake" interrupt.
	; stuff
	; stuff
	; stuff
	; stuff

	;-------------------------------------------------
	; Poll (check) for our "fake" int here!
	;-------------------------------------------------
						; all we do is check if timer0,bit7 
						; has changed, we compare it to our flag.
						; if so we generate a "fake" interrupt.
						;
	btfsc TMR0,7			; if bit7 is lo, force flag lo too
	goto timer_7hi			;

						; TMR0,7 is lo, check if flag lo too
	btfss bit7flag,7		; skip if mismatch
	goto main_loop			; just keep looping

						; we detected a mismatch, so do fake int!
	bcf bit7flag,7			; fix flag first
	goto fake_int			;

timer_7hi					; TMR0,7 is hi

	btfsc bit7flag,7		; test if bit7 hi AND flag lo
	goto main_loop			; flag also hi, no fake int, keep looping.

						; we detected a mismatch, so do fake int!
	bsf bit7flag,7			; fix flag first

	;-------------------------------------------------
fake_int					
	; 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.
	;-------------------------------------------------
						; * 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.

	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, like add
	; one second to our clock or whatever.
	; (in this example we toggle a led)

	; the other thing we need to do is add 1,000,000 counts
	; to our 24bit variable and start all over again.
	;-------------------------------------------------
						; Add the 1,000,000 counts first.
						; One second = 1,000,000 = 0F 42 40 (in hex)

						; 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 0x0F			; get msb value 
	movwf bres_hi			; load in msb

	movlw 0x42			; get mid value
	movwf bres_mid			; load in mid

	movlw 0x40			; 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 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 we do the "event" that we do every one second.

						; Note! for this example we toggle a led, which
						; will give a flashing led which is on for a second
						; and off for a second.
						; Add your own code here for your one second event.

						; Note! My led is on porta,3
						; your led may be on a different pin.
	movlw b'00001000'		; mask for bit 3
	xorwf PORTA,f			; toggle PORTA,bit3 (toggle the led)
						
	;-------------------------------------------------
						; now our one second event is all done,
						; we can keep looping.
	goto main_loop			;

;------------------------------------------------------------------------------


;==============================================================================
	end					; no code after this point.
;==============================================================================















