Continue to Site

# PIC One Second Algorithm from Roman Black

Status
Not open for further replies.

#### BlackOps

##### Full Member level 5
mikrobasic retfie

Hello, i think most of you probably saw this webpage:
https://romanblack.com/one_sec.htm

it is about how to make exactly 1 second delay on PICs using interrupts.

but my problem is i absolutely dont understand the heart of this algorithm.
here is a fragment of code:

he declares three variables, and assigns them values:
(in DEcimal) bres_hi = 15, bres_mid = 66 + 1, bres_lo = 64

this is done in setup routine which runs only once:
Code:
movlw    0x0F
movwf    bres_hi

movlw     0x42 +1
movwf    bres_mid

movlw    0x40
movwf    bres_lo

and this code, which contains counting algorithm for one second runs in the interrupt handler:

movf bres_mid,1
btfsc STATUS,2
decf bres_hi,f
decfsz bres_mid,f
goto int_exit

movf bres_hi,1
btfss STATUS,2
goto int_exit

movlw 0x0F
movwf bres_hi

movlw 0x42 +1
movwf bres_mid

movlw 0x40
btfss STATUS,0
incf bres_mid,f

;MY EVENT WHICH OCCURS EVERY ONE SECOND

int_exit
bcf INTCON,T0IF
movf status_temp,w
movwf STATUS
swapf w_temp,f
swapf w_temp,w
retfie

i dont get how does he get one second event from those numbers in decimal, 15, 66+1 and 64?

can anyone explain me this with formula?

and for example show me how is it possible to remake this code work for every...say....2....5....10 seconds?

timer0 accurate one second

I suspect he is using timer 0 to generate interrupts. Timer 0 is a binary counter, with or without a prescaler. Without a prescaler it increments every instruction cycle, that is at 1/4 the oscillator frequency. It looks like he is not loading TMR0 with anything.
So you get an interrupt every 256 instruction cycles. Depending on the actual oscillator frequency this can mean different time intervals. So then you need to decrement an appropriately chosen constant to achieve 1 second.

For example, let's assume the oscillator is 4MHz. That means TMR0 clock is 1MHz, or it increments every 1 us. It then overflows and generates an interrupt every 256 us. Then, to obtain 1 second, you need to see 1s/ 256us=3906.25 TMR0 interrupts. Then at 4MHz, that would be your constant, 3096.
But if the oscillator runs at, say, 3.6864 MHz, then you need to adjust the constant accordingly.

Personally, whenever I need something like this, I pre-load TMR0 with some number so as to get a nice round number, say an interrupt every 250us. Then your constant is a round 4000. Even if it is not such a nice number, at least you may be able to remove or reduce the fractional part. Note that original constant is 3096.25. You actually lose the .25 if the constant is 3096, which means you do not get EXACTLY 1s as you claim.

For the 4MHz case you would need to pre-load TMR0 with 6, because you want to remove 6 counts from it (remember, it increments and causes an interrupt when it rolls over from 255 to 00, that is 256 counts, so you want to lose 6 and so pre-load the counter with 6).
However, TMR0 does not increment for two cycles after you write to it and since pre-loading it means writing to it, you would "gain" an additional 2 counts. Therefore, you would need to pr-load it with 8, rather than 6.
In additon, since you pre-load the timer in the interrupt routine, some time will be lost until you get to the actual movwf TMR0 instruction (context saving, etc.), so the number has to be adjusted to take into account those instructions.

If you use the pre-scaler, which can be used either by TMR0 or by the WDT, then the calculation changes, based on the pre-scaler divider ratio, which you can select in S/W.

Normally, I use just straight binary numbers for the constants, since you can just use decfsz instructions to decrement the constant. And if you need more than 1s, then make the constant larger.

I hope this helps.

### BlackOps

Points: 2
mplab pic tmr interrupt example

VVV thanx ur post Helped me a little. and...that program really does 1 sec. ok, but problem is not this.

i want to do manually EXACT one second delay program on PIC16F88 chip. using TMR0 adjusting.

i wrote the code. but it doesnt work correct. here is my calculation:

Delay = (4 * 2 * 100 * 100 * 50) / 4 000 000 = 1.000 000 second
so as u see here i must get EXACT 1 second

so as you see now, i set Prescaler to 1:2, make TMR0 count every 100 times. and create two variables, count = 100, and count2 = 50
now i must get 1 second.... BUT, my code doesnt work good, and i get: 0.076510 seconds
It is NOT correct... can anyone say me why do i get such value? where is my error?
how must i remake my code for 1 sec?

well, and here is my code which gives wrong result

Code:
;***********************************************************************************
;
;
;
;
;***********************************************************************************

#include <p16F88.inc>

__CONFIG	_CONFIG1, _CP_OFF & _CCP1_RB0 & _DEBUG_ON & _WRT_PROTECT_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _MCLR_ON & _PWRTE_ON & _WDT_OFF & _INTRC_IO

status_temp
w_temp
count						; first count variable
count2						; second count variable
ENDC

START
goto	SETUP

;***********************************************************************************
; START OF THE INTERRUPT SERVICE ROUTINE
;***********************************************************************************
INTERRUPT

movwf	w_temp
swapf	STATUS, w
movwf	status_temp;

movf	count,w			;move count to w
xorlw	b'00000000'		;XOR literal with w

btfss	STATUS,2		;if count iz zero, then skip next instruction
decfsz	count, f		;decrement count, exit ISR if count iz zero
goto	count2dec
goto	INTEXIT

count2dec:
movf	count,w			;move count to w
xorlw	b'00000000'		;XOR literal with w

btfsc	STATUS,2		;skip next instruction if count is not 0
decfsz	count2,f		;decrement count2, turn On/Off LED if zero
goto INTEXIT

movlw 	b'10000000'
xorwf 	PORTB,f

movlw 	.100
movwf	count
movlw 	.50
movwf	count2

movlw	.158			;TMR0 must count 100 times, so it must have 156 initially
movwf	TMR0			;it takes 2 instruction cycles to write to TMR0, so i put 158 there

INTEXIT
bcf	STATUS,2			;clearing Z bit in STATUS, i think i must do it?
bcf INTCON, TMR0IF

swapf status_temp, w
movwf STATUS
swapf w_temp, f
swapf w_temp, w

retfie
;***********************************************************************************
; END OF THE INTERRUPT SERVICE ROUTINE
;***********************************************************************************

;***********************************************************************************
; START OF THE SETUP ROUTINE (RUNS ONLY ONCE)
;***********************************************************************************
SETUP
banksel	OPTION_REG
;	movlw	0xc7
movlw	0xc0				;prescaler is 1:2
movwf	OPTION_REG

banksel	TRISB
movlw	b'00000000'
movwf	TRISB

banksel PORTB
clrf	PORTB

banksel OSCCON
movlw 	b'01100000' ; should be 4 mhz
movwf 	OSCCON

banksel	INTCON
clrf	INTCON

movlw	.100
movwf	count
movlw 	.50
movwf	count2

movlw	0xe0
movwf	INTCON

movlw	.158			;TMR0 must count 100 times, so it must have 156 initially
movwf	TMR0			;it takes 2 instruction cycles to write to TMR0, so i put 158 there

;***********************************************************************************
; END OF THE SETUP ROUTINE
;***********************************************************************************

;###################################################################################
; MAIN PROGRAM START
;###################################################################################
MAINLOOP
nop
nop
nop
goto MAINLOOP
;###################################################################################
; MAIN PROGRAM END
;###################################################################################

END

i'd appreciate ur help guys,thanks

longint a byte mikrobasic

First off the 4MHz INTOSC is not exactly 4MHz. It's no good for a real time clock but it's good enough for serial communications.
It's 1% calibrated at room temperature 23c and will change its speed depending on the temperature.

You'll need a crystal to get anywhere near clock accuracy.

Also look into using TMR2 or the CCP1 special interrupt as they are much more versatile than TMR0

swordfish prescaler timer

First of all, if you need to test a register if it's zero, you only need to do a movf reg,f and then test the Z flag, you do not need to xor with w. But why do it at all, ince decfsz does it for you?

I think the problem with your code is that you do not reload counter 1 when it reaches zero. This is how I would do it:
Code:
decfsz count,f
goto intexit

movwf count
decfsz count2,f
goto intexit

movwf count2

;and here you reload TMR0, do what you need to do, etc. because 1 sec has elapsed.

Obviously, the total delay will depend on the product of the constants you load in the two counters.

Another method is to use a 16-bit counter, but in 2's complement. That way you can increment the easily increment the 16-bit quantity.
Code:
movlw high (.65536-COUNT)
movwf count2
movlw low (.65536-COUNT)
movwf count;

Int_vector:
movwf   w_temp          ;save context
swapf   STATUS, w
movwf   status_temp;

incfsz count
goto intexit

incfsz count2
goto intexit

;here reload count and count2 as above, do what you need to do, etc. then exit.

This second method is perhaps more useful when your counter has to be larger than 255 and it cannot me thought of as the product of two numbers.
The constant COUNT is the actual number you need. For example, if you need to count 3657 TMR0 interrupts to get your time interval, then that is COUNT.

And no, you do not need to clear the Z bit before exiting the interrupt. (By the way, Z will work just fine, why bother with numbers?)

### BlackOps

Points: 2
tmr0 register swordfish

blueroomelectronics: i was measuring time in MPLAB SIM. but thanx for advice i'll take it in notice for future designs.

VVV: well that was much more helpful! thanks!! but i still have some little problems.
i have remaked ISR code fragment as you said (by the way in my routine i had a 100 + 50, not 100 * 50 formula...). and then i also disabled clearing of INTCON register in SETUP routine... because with this piece of code i had always 0 in TMR0.

So...anyway, now i get number 1.279999 seconds... still not 1.000 000 seconds.

now my count and count2 variables are OK... then...? what can be the other problem?

maybe something wrong to TMR0 writing? (as u see i do input of 158, cuz of loss of 2 cycles during write to TMR0...as said in MIdRange manual)

please check if i am doing it correctly? and is it normal to clear TMR0IF bit in the INTEXIT code?

here is FULL code again:
Code:
;***********************************************************************************
;
;
;
;
;***********************************************************************************

#include <p16F88.inc>

__CONFIG   _CONFIG1, _CP_OFF & _CCP1_RB0 & _DEBUG_ON & _WRT_PROTECT_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _MCLR_ON & _PWRTE_ON & _WDT_OFF & _INTRC_IO

status_temp
w_temp
count                  ; first count variable
count2                  ; second count variable
ENDC

START
goto   SETUP

;***********************************************************************************
; START OF THE INTERRUPT SERVICE ROUTINE
;***********************************************************************************
INTERRUPT

movwf   w_temp
swapf   STATUS, w
movwf   status_temp;

decfsz   count, f      ;decrement count, exit ISR if count iz zero
goto   INTEXIT

movlw    .100
movwf   count

decfsz   count2,f      ;decrement count2, turn On/Off LED if zero
goto INTEXIT

movlw    .50
movwf   count2

movlw   .158         ;TMR0 must count 100 times, so it must have 156 initially
movwf   TMR0         ;it takes 2 instruction cycles to write to TMR0, so i put 158 there

movlw    b'10000000'
xorwf    PORTB,f

INTEXIT

bcf INTCON, TMR0IF

swapf status_temp, w
movwf STATUS
swapf w_temp, f
swapf w_temp, w

retfie
;***********************************************************************************
; END OF THE INTERRUPT SERVICE ROUTINE
;***********************************************************************************

;***********************************************************************************
; START OF THE SETUP ROUTINE (RUNS ONLY ONCE)
;***********************************************************************************
SETUP
banksel   OPTION_REG
;  movlw   0xc7
movlw   0xc0            ;prescaler is 1:2
movwf   OPTION_REG

banksel   TRISB
movlw   b'00000000'
movwf   TRISB

banksel PORTB
clrf   PORTB

banksel OSCCON
movlw    b'01100000' ; should be 4 mhz
movwf    OSCCON

;   banksel   INTCON
;   clrf   INTCON

movlw   .100
movwf   count
movlw    .50
movwf   count2

movlw   0xe0
movwf   INTCON

movlw   .158         ;TMR0 must count 100 times, so it must have 156 initially
movwf   TMR0         ;it takes 2 instruction cycles to write to TMR0, so i put 158 there

;***********************************************************************************
; END OF THE SETUP ROUTINE
;***********************************************************************************

;###################################################################################
; MAIN PROGRAM START
;###################################################################################
MAINLOOP
nop
nop
nop
goto MAINLOOP
;###################################################################################
; MAIN PROGRAM END
;###################################################################################

END

roman black algorithm

I am not sure why but the problem in the simulator seems to be failure when writing 158 to the TMR0 inside the interrupt service routine.

regards

narccizzo...NO, MPLAB SIM can handle interrupts but in run mode, not in debug mode, and i can see RBx outputs of chip on diagram.. i also tested this in PROTEUS. same result.

so problem is somewhere in reloading TMR0... but what exactly is wrong...?

I see the problem with TMR0: you need to reload it BEFORE you decrement the counters, else it will simply continue counting, roll over to zero, etc. It will only get reloaded once every time the counters reach zero, which is not what you want.

Now the thing is I tried running the code in the simulator, but for some reason the TMR0 does not get loaded and it incrments every instruction cycle, instead of every other. I think this is just a simulator problem. If you move the TMR0 reloading code just before the decrementing the counters, it should work.

And yes, you must clear TMR0IF before you exit the ISR, or else you will immediately enter it again, since the micro just tests the flag and acts accordingly.

One more thing to check: as I recall, TMR0 does not get incremented for 2 instruction cycles only if the prescaler is set to 1 or assigned to the WDT. I think the statement refers to the entire 16-bit counter, TMR0+ prescaler, so you do not need to reload TMR0 with 158, but 157, because your prescaler is 1:2, so losing two instruction cycles means 1 lost count in TMR0.

### BlackOps

Points: 2
more work on problem

VVV,thanks for reply again. but it again doesnt work...

so what i did is, i moved this piece of code:
movlw .157
movwf TMR0

before decrementing count variable in the ISR.

and i also assigned .157 to TMR0 in the SETUP routine...

interesting thing is, the result was same 1.279999 seconds... it didnt matter did i load .157 or .158 in the TMR0 both in SETUP and ISR... or did i load both of them with .157, the result was same 1.279999 WHY? its very strange... and my code stil doesnt work i cant get one second.

you say i must load TMR0 with N+1.. not N+2, but in MId Range manual of Microchip, in section of Timer0 they say that i must load it with N+2...

but as i said before now, it didnt matter did i load it with n+1 or n+2 i had same wrong result... hmm...strange, any ideas?

p.s. i was measuring time both in PROTEUS and MPLAB SIM. two things cant be wrong

Added after 1 hours 22 minutes:

well well...

now i've noticed interesting things.
i have put banksel TMR0 in front of every operation with TMR0.
and i did load TMR0 with .156 both in ISR and SETUP.
the result was: 1.055 000 seconds

then i decided to make reverse calculation and find out which number does TMR0 counting to.

1.055 000 = (4 * 2 * X * 5000) / 4 000 000 ===> X = 105.5

i wonder why didnt i get Integer number?
so...TMR0 is counting to 105.5... then, it means i am loosing 5.5 cycles..
then it means i must reload TMR0 with 156 + 5.5 ?? seems so, but not possible.
then i tried to reload TMR0 with 156 + 6, or 162...
and i did get the result of: 0.995000 seconds.

OK, any ideas now?

OK, only now did I notice you were not working in bank zero in teh main loop
Anyway, my mistake: add clrf STATUS, right after saving the context. That will bring you to bank 0.

As for loading TMR0, you need to take into account the interrupt delay (2 cycles I think), plus the context saving (3 cycles) plus the clrf STATUS, plus the TMR0 reload instructions (2 cycles), plus the delay it takes TMR0 to increment (2 cycles).
That is a total of 10 cycles. I loaded TMR0 with 166 and the interrupts occur at 100us.

The thing with 0.5 cycles can be solved by adding a nop before reloading TMR0. Since you use a 1:2 prescaler, the extra instruction will count like 1/2 cycle. In this case the clrf STATUS did the job.

Code:
INTERRUPT

movwf   w_temp
swapf   STATUS, w
movwf   status_temp;

clrf	STATUS
movlw   .166         ;TMR0 must count 100 times, so it must have 156 initially
movwf   TMR0         ;it takes 2 instruction cycles to write to TMR0, so i put 158 there

decfsz   count, f      ;decrement count, exit ISR if count iz zero
goto   INTEXIT

movlw    .100
movwf   count

decfsz   count2,f      ;decrement count2, turn On/Off LED if zero
goto INTEXIT

movlw    .50
movwf   count2

movlw    b'10000000'
xorwf    PORTB,f

INTEXIT

bcf INTCON, TMR0IF

swapf status_temp, w
movwf STATUS
swapf w_temp, f
swapf w_temp, w

retfie

### BlackOps

Points: 2
Using the prescaler will certainly introduce accuracy and timing errors. You must maintain 1:1 timer operation to achieve RTC accuracy.

but again i didnt get 1.000.000 seconds with ur new code also...
i do get 0.955 seconds! so i do loose 45000us, where and why?

i preloaded TMR0 with .176 in the SETUP routine, and the ISR was as u showed me... and i got delay of 0.955 seconds! still something is wrong!

but it doesnt matter now what i do preload TMR0 in the SETUP routine with... same 955000us delay... where do i loose 45000us?

Added after 2 hours 4 minutes:

Well well..
at last i did it. first let me post my final code which gives exact one second, according to MPLAB SIM. here it is:
THE WORKING CODE OF EXACT 1 SECOND DELAY USING Timer0 MODULE AND PRELOADING OF TMR0
Code:
;***********************************************************************************
; Program:
; Author:
; Processor:	PIC16F88
; Description:
; This is a program for demonstrating exact one second delay using
; interrupts and Timer0 module. The general formula for calculating the delay is:
; ***************************************************************************
; * Delay = (4 * Prescaler * (256 - TMR0) * count * count2) / Crystal Freq. *
; ***************************************************************************
; In our case we preload TMR with the value which makes it count to 100.
; Select prescaler of 1:2
; and assign values of 100 and 50 to count and count2 respectively
; after that we have exactly one second:
; 1 second = (4 * 2 * (256 - 156) * 100 * 50) / 4 000 000
;***********************************************************************************

#include <p16F88.inc>

__CONFIG   _CONFIG1, _CP_OFF & _CCP1_RB0 & _DEBUG_ON & _WRT_PROTECT_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _MCLR_ON & _PWRTE_ON & _WDT_OFF & _INTRC_IO

status_temp
w_temp
count                  		; first count variable
count2                  	; second count variable
ENDC

START
goto   	SETUP

;***********************************************************************************
; START OF THE INTERRUPT SERVICE ROUTINE
;***********************************************************************************

INTERRUPT

movwf   w_temp			; Saving context
swapf   STATUS, w
movwf   status_temp;

nop						; we use it to waste 0.005 seconds, otherwise we get delay of 0.995 seconds
banksel	TMR0
movlw   	.162        	; TMR0 must count 100 times, so it must have 156 initially
movwf   	TMR0        	; but we also have another cycles which we loose so we must add them to TMR0 also
; those cycles are:
; context saving	-	3
; banksel command	-	1

decfsz   count, f      	; decrement count, exit ISR if count is not zero
goto   	INTEXIT			; if count is zero skip INTEXIT, and reload it with .100 again
movlw    .100
movwf   	count

decfsz   count2,f      	; then decrement count2, exit ISR if count2 is not zero
goto 	INTEXIT 		; if count2 is zero, skip INTEXIT and reload it with .50 again
movlw    .50
movwf   	count2

movlw    b'10000000'    	; switch PORTB pin
xorwf    PORTB,f

INTEXIT

bcf 		INTCON, TMR0IF 	; Clearing TMR0IF after exit from ISR

swapf 	status_temp, w 	; Restoring context
movwf 	STATUS
swapf 	w_temp, f
swapf 	w_temp, w

retfie
;***********************************************************************************
; END OF THE INTERRUPT SERVICE ROUTINE
;***********************************************************************************

;***********************************************************************************
; START OF THE SETUP ROUTINE (RUNS ONLY ONCE)
;***********************************************************************************

SETUP
banksel  OPTION_REG
movlw   	0xc0            ; Prescaler is 1:2
movwf   	OPTION_REG

banksel  TRISB 			; PORTB is all set to Output
movlw   	b'00000000'
movwf  	TRISB

banksel 	PORTB 			; clearing PORTB
clrf   	PORTB

banksel 	OSCCON
movlw    b'01100000' 	; 4Mhz oscillator frequency select
movwf    OSCCON

movlw   	.100 			; assing values to count variables
movwf  	count
movlw    .50
movwf   	count2

movlw   	0xe0 			; GIE = 1, PEIE = 1, TMR0IE = 1
movwf   	INTCON

banksel	TMR0			; initially clear TMR0,though its not necessary
clrf		TMR0

;***********************************************************************************
; END OF THE SETUP ROUTINE
;***********************************************************************************

;###################################################################################
; MAIN PROGRAM START
;###################################################################################

MAINLOOP
nop
nop
nop
goto MAINLOOP

;###################################################################################
; MAIN PROGRAM END
;###################################################################################

END

now what i did is i removed any preloading of TMR0 in the SETUP routine. i only cleared TMR0 there.

next i preloaded TMR0 with 162 because i have the following cycle loose:
context saving - 3 cycles
banksel TMR0 - 1 cycle

it is total of 6 cycles.

but after this i got value of 0.995 seconds, and as you VVV said to me, i added nop before preloading of TMR0, now it seems to work. nop wastes 0.005 seconds.

here is the table of RB7 output from Logic Analyser
Code:
RB7(X)	RB7(Y)
11	0
1000359	1
2000359	0
3000359	1
4000359	0
5000359	1
6000359	0
6049227	0

here is graph of MPLAB SIM Logic Analyzer:

the only thing which is strange, that 11 number in the table... and last two results are 0... but the timing is exactly 1 second there (if u subtract values from table)

so, i hope others would find this code useful.

i will also post here again later, with another method of getting this number, without preloading TMR0, but better than the one which used Roman Black

and one more thing! VVV you was right! Roman Black did not get EXACT one second! i measured his delays using MPLAB SIM, and it showed me the delay of 1.000192 seconds! dont know if someone else did the same? then i measured my final program and it did give 1.000.000 delay.

and one more question about my final program, as you know i do get delay of 1.000.000 seconds in MPLAB SIM. but in PROTEUS, using COUNTER tool i do get delay of 1.005.000 seconds, WHY?

however, if i remove NOP from the ISR before TMR0 preloading, i do get 1.000.000 second delay in PROTEUS, but then i do get 0.995.000 seconds in MPLAB SIM. heh... why is it so? any idea?

It may be that MPLAB does not simulate the 2-cycle delay needed to branch to the interrupt vector. I am not 100% sure about that, though.
When you build your actual circuit you will be able to measure the delay on the pin. After all, the ultimate goal is to get the right time in the real world. If it's not exaclty what you expect, at least you know what to do to correct the problem.

When you have a timer prescaler set to something other than 1:1 you will introduce some error which you cannot compensate for, even using Roman Black's method. You must have a 1:1 Timer to Tosc ratio to build an accurate time base.

The problem with writing a 'hard' value to the TMR0 register is that there may be a 1 cycle 'jitter' entering the ISR when the Timer 0 'overflow' interrupt occurs during a 1 cycle or a 2 cycle instruction in your MAIN program. A better approach would be to add your 'reload' value to TMR0.

Another caveat is that writing to TMR0 will clear the prescaler which could mess up your timing so you probably want to keep a 1:1 prescaler setting. This limits you to rather short interrupt intervals but I'm sure you can still come up with a workable solution.

If I were forced to use Timer 0 for a RTC "heartbeat" instead of Timer 2, I might do it something like this (untested code);

Good luck with your project. Mike

Code:
;******************************************************************

org     0x0004
;
;  save main program context
;
ISR_Proc
movwf   W_ISR           ; save W-reg                      |B?
swapf   STATUS,W        ; doesn't change STATUS bits      |B?
movwf   S_ISR           ; save STATUS reg                 |B?
clrf    STATUS          ; bank 0                          |B0
movf    PCLATH,W        ; get PCLATH                      |B0
movwf   P_ISR           ; save PCLATH                     |B0
clrf    PCLATH          ; ISR is in bank 0                |B0
;
;  prep TMR0 for next interrupt cycle and bump 1 second counters
;
movlw   -d'250'+2       ; value for 250-usec interrupts   |B0
decfsz  RTCL,F          ; lo counter 0? yes, skip, else   |B0
goto    ISR_Exit        ; exit ISR                        |B0
decfsz  RTCH,F          ; hi counter 0? yes, skip, else   |B0
goto    ISR_Exit        ; exit ISR                        |B0
;
;
movlw   low(d'4000')    ; reload our 1-second counters    |B0
movwf   RTCL            ;                                 |B0
movlw   high(d'4000')+1 ;                                 |B0
movwf   RTCH            ;                                 |B0
;
;  perform our once-per-second routines here
;
nop                     ;                                 |B0
;
;  restore main program context
;
ISR_Exit
bcf     INTCON,TMR0IF   ; clear TMR0 interrupt flag bit   |B0
movf    P_ISR,W         ;                                 |B0
movwf   PCLATH          ; restore PCLATH                  |B0
swapf   S_ISR,W         ;                                 |B0
movwf   STATUS          ; restore STATUS                  |B0
swapf   W_ISR,f         ; don't screw up STATUS           |B0
swapf   W_ISR,W         ; restore W-reg                   |B0
retfie                  ; return from interrupt           |B0

;******************************************************************

Here is something I wrote a while back for the mikroBasic for PIC forums. It's code to show how to achieve precision PIC time bases.

Interrupts are necessary for an accurate RTC program. Interrupts make it possible to create an accurate time base with virtually unnoticable effects on the program as a whole. The issue with interrupts is the code overhead of saving important registers before getting to the programmer's interrupt code, such as reloading the timer and preparing for the next interrupt cycle. Timers rollover to zero which creates the interrupt but then continue running for several cycles before the programmer gets a chance to reload it. Often it is difficult to predict the length of time that the timer has continued to run before reloading it with a fixed time value. That value must be adjusted somehow. The examples below are intended to show how to compensate for this "lost" time with some clever software techniqes and methods.

About the methods. The first 2 programs utilize the Zero-Sum, or Zero Cumulative Error, method for maintaining accurate time. It's essentially a method of reloading a timer and automatically compensating for ISR code overhead by adding to the existing timer value. Remember that timers increment, count up, to rollover, or 0. If you add to their value you are actually shortening their trip to 0. So we can compensate for cycles unaccounted for by adding to the timer's value. It's too much to explain further here but is well explained by ROMAN BLACK on his webpage:

https://www.romanblack.com/one_sec.htm

The last 2 are period based free running timer methods using PR2 with Timer2 and Special Event Feature of Timer1 and the CCP1 module. Both methods automatically reset the timer when the values in PR2 and CCPR1 registers match their respective timer.

Each program was tested on a 18F452 @ 8MHz. The programs can also be used with PIC16's with a few changes. All source-code will compile with the free demo version of the compiler. I have added code to each program method to demonstrate a 24 Hour Time Clock output on a 2 line LCD.

TIMER1 - Zero-Sum with Timer Stop Method

Code:
program RTC_Timer1_18F452

' 24 Hour Real Time Clock - Zero-Sum Timer Stop Method
' mikroBasic 5.02 ... 18F452 @ 8MHz Default Configuration
' Uses Timer1; No Prescaler; 2 counts per microsecond
' Add 6 cycles for Timer1 stop, reload, and restart in ISR

dim T1 as word absolute $FCE ' Timer1 Low and High dim T1_Period as word ' Timer1 reload value + fudge factor dim int_count as byte ' interrupt counter dim update as boolean ' update output dim secs, mins, hrs as byte ' tally variables dim clk as string[9] absolute$20         ' output string for LCD
dim hstr as string[3] absolute $20 dim mstr as string[3] absolute$23
dim sstr as string[3] absolute $26 sub procedure interrupt T1CON.TMR1ON = 0 ' stop Timer1 T1 = T1 + T1_Period ' reload Timer1 + zero-sum error T1CON.TMR1ON = 1 ' start Timer1 If inc(int_count) = 40 Then ' 40 counts x 25000us = 1sec int_count = 0 ' restart interrupt counter update = true ' advance seconds & update output End If PIR1.TMR1IF = 0 ' clear interrupt flag end sub sub procedure Clock24 If inc(secs) = 60 Then ' check each for rollover secs = 0 If inc(mins) = 60 Then mins = 0 If inc(hrs) = 24 Then hrs = 0 End If End If End If bytetostr(hrs, hstr) ' create clock output string for LCD If hstr[1] = 32 Then hstr[1] = 48 End If bytetostr(mins, mstr) mstr[0] = ":" If mstr[1] = 32 Then mstr[1] = 48 End If bytetostr(secs, sstr) sstr[0] = ":" If sstr[1] = 32 Then sstr[1] = 48 End If LCD_CMD(130) LCD_OUT_CP("24-HOUR CLOCK") LCD_CMD(195) LCD_OUT_CP(clk) update = false end sub sub procedure Initialize ADCON1 = 15 int_count = 0 secs = 255 mins = 0 hrs = 0 update = true LCD_INIT(PORTB) LCD_CMD(LCD_CURSOR_OFF) INTCON.GIE = 1 ' enable GIE INTCON.PEIE = 1 ' enable PEIE T1_Period = -50000 + 6 ' 25000us interrupt + 6 cycles Timer1 reload T1CON = 0 ' no prescaler and Timer1 is Off T1 = T1_Period ' load Timer1 PIE1.TMR1IE = 1 ' interrupt enabled PIR1.TMR1IF = 0 ' interrupt flag cleared T1CON.TMR1ON = 1 ' start Timer1 end sub main: Initialize While true ' loop forever If update <> false Then ' advance seconds? Clock24 ' yes...then output End If Wend end. TIMER0 - Zero-Sum Free-Running Timer Method Code: program RTC_Timer0_FreeRun_18F452 ' 24 Hour Real Time Clock - Zero-Sum Timer FreeRun Method ' mikroBasic 5.02 ... 18F452 @ 8MHz Default Configuration ' by Warren Schroeder on May 15, 2007 ' Timer0 in 8-bit mode; No Prescaler; 2 counts per microsecond ' ' 1 second = 2000000 clock cycles - 2 for clock restart const OneSecond as longint = 2000000 - 2 dim Cyc_Counter as longint absolute$30   ' Timer0 cycle counter
dim T0Load as byte absolute $30 ' Timer0 reload value dim Countdown as word absolute$31        ' # of 8-bit cycles
dim update as boolean                     ' update output
dim secs, mins, hrs as byte               ' tally variables
dim clk as string[9] absolute $20 ' output string for LCD dim hstr as string[3] absolute$20
dim mstr as string[3] absolute $23 dim sstr as string[3] absolute$26

sub procedure interrupt
If dec(Countdown) = 0 Then
Cyc_Counter = OneSecond             ' reload timer
If Status.C = 0 Then
inc(Countdown)                   ' if no carry increment counter once
End If
update = true                       ' flag to update clock output
End If
INTCON.TMR0IF = 0                      ' clear interrupt flag
end sub

sub procedure Clock24
If inc(secs) = 60 Then                ' check each tally for rollover
secs = 0
If inc(mins) = 60 Then
mins = 0
If inc(hrs) = 24 Then hrs = 0 End If
End If
End If
bytetostr(hrs, hstr)                  ' create clock output string for LCD
If hstr[1] = 32 Then hstr[1] = 48 End If
bytetostr(mins, mstr)
mstr[0] = ":"
If mstr[1] = 32 Then mstr[1] = 48 End If
bytetostr(secs, sstr)
sstr[0] = ":"
If sstr[1] = 32 Then sstr[1] = 48 End If
LCD_CMD(130)
LCD_OUT_CP("24-HOUR CLOCK")
LCD_CMD(195)
LCD_OUT_CP(clk)
update = false
end sub

sub procedure Initialize
secs = 255
mins = 0
hrs = 0
update = true
Cyc_Counter = OneSecond
inc(CountDown)
LCD_INIT(PORTB)
LCD_CMD(LCD_CURSOR_OFF)
INTCON.GIE = 1                         ' enable GIE
INTCON.PEIE = 1                        ' enable PEIE
T0CON = 72                             ' 8-bit; no prescaler; Timer0 off
TMR0L = 0                              ' clear timer1
INTCON.TMR0IE = 1                      ' interrupt enabled
INTCON.TMR0IF = 0                      ' interrupt flag cleared
T0CON.TMR0ON = 1                       ' start Timer0
end sub

main:
Initialize

While true                             ' loop forever
If update = true Then               ' advance seconds?
Clock24                          ' yes...then output
End If
Wend

end.

TIMER2 - PR2 Compare Free-Running Timer Method

Code:
program RTC_PR2_FreeRun_18F452

' 24 Hour Real Time Clock - Timer2 with PR2 Reset FreeRun Method
' mikroBasic 5.02 ... 18F452 @ 8MHz Default Configuration
' by Warren Schroeder on May 15, 2007
' Timer2; Prescaler=0; 2 clock every 1us; PR2=249
' Match and reset every 0.5us x 249 = 124.5us + 1 cycle for TMR2 restart

dim countdown as word                     ' interrupt counter
dim update as boolean                     ' update output
dim secs, mins, hrs as byte               ' tally variables
dim clk as string[9] absolute $20 ' output string for LCD dim hstr as string[3] absolute$20
dim mstr as string[3] absolute $23 dim sstr as string[3] absolute$26

sub procedure interrupt
If dec(countdown) = 0 Then
countdown = 8000                    ' 8000 x 125us = 1 second
update = true                       ' flag to update clock output
End If
PIR1.TMR2IF = 0                        ' clear interrupt flag
end sub

sub procedure Clock24
If inc(secs) = 60 Then                ' check each tally for rollover
secs = 0
If inc(mins) = 60 Then
mins = 0
If inc(hrs) = 24 Then hrs = 0 End If
End If
End If
bytetostr(hrs, hstr)                  ' create clock output string for LCD
If hstr[1] = 32 Then hstr[1] = 48 End If
bytetostr(mins, mstr)
mstr[0] = ":"
If mstr[1] = 32 Then mstr[1] = 48 End If
bytetostr(secs, sstr)
sstr[0] = ":"
If sstr[1] = 32 Then sstr[1] = 48 End If
LCD_CMD(130)
LCD_OUT_CP("24-HOUR CLOCK")
LCD_CMD(195)
LCD_OUT_CP(clk)
update = false
end sub

sub procedure Initialize
secs = 255
mins = 0
hrs = 0
update = true
countdown = 8000
LCD_INIT(PORTB)
LCD_CMD(LCD_CURSOR_OFF)
INTCON.GIE = 1                         ' enable GIE
INTCON.PEIE = 1                        ' enable PEIE
T2CON = 0                              ' prescaler=0; postscaler=0; Timer2=off
TMR2 = 0                               ' clear timer2
PR2 = 249                              ' TMR2 resets when matching PR2
PIE1.TMR2IE = 1                        ' interrupt enabled
PIR1.TMR0IF = 0                        ' interrupt flag cleared
T2CON.TMR2ON = 1                       ' start Timer0
end sub

main:
Initialize

While true                             ' loop forever
If update = true Then               ' advance seconds?
Clock24                          ' yes...then output
End If
Wend

end.

TIMER1 - CCPR1 Compare Free-Running Timer Method

Code:
program RTC_CCP1_FreeRun_18F452

' 24 Hour Real Time Clock - Timer1 with CCP1 Reset FreeRun Method
' mikroBasic 5.02 ... 18F452 @ 8MHz Default Configuration
' by Warren Schroeder on May 15, 2007
' Timer1; Prescaler=0; 2 clock every 1us; Timer1 interrupt Disabled
' CCPR1L:CCPR1H compare value for TMR1; on match sets CCP1IF & resets TMR1
' Match and reset every 0.5us x (50000 - 1) = 24499.5us + 1 cycle for TMR1 restart

dim C1 as word absolute $FBE dim countdown as byte ' interrupt counter dim update as boolean ' update output dim secs, mins, hrs as byte ' tally variables dim clk as string[9] absolute$20         ' output string for LCD
dim hstr as string[3] absolute $20 dim mstr as string[3] absolute$23
dim sstr as string[3] absolute \$26

sub procedure interrupt
If dec(countdown) = 0 Then
countdown = 40                      ' 40 x 25000us = 1 second
update = true                       ' flag to update clock output
End If
PIR1.CCP1IF = 0                        ' clear interrupt flag
end sub

sub procedure Clock24
If inc(secs) = 60 Then                ' check each tally for rollover
secs = 0
If inc(mins) = 60 Then
mins = 0
If inc(hrs) = 24 Then hrs = 0 End If
End If
End If
bytetostr(hrs, hstr)                  ' create clock output string for LCD
If hstr[1] = 32 Then hstr[1] = 48 End If
bytetostr(mins, mstr)
mstr[0] = ":"
If mstr[1] = 32 Then mstr[1] = 48 End If
bytetostr(secs, sstr)
sstr[0] = ":"
If sstr[1] = 32 Then sstr[1] = 48 End If
LCD_CMD(130)
LCD_OUT_CP("24-HOUR CLOCK")
LCD_CMD(195)
LCD_OUT_CP(clk)
update = false
end sub

sub procedure Initialize
secs = 255
mins = 0
hrs = 0
update = true
countdown = 8000
LCD_INIT(PORTB)
LCD_CMD(LCD_CURSOR_OFF)
INTCON.GIE = 1                         ' enable GIE
INTCON.PEIE = 1                        ' enable PEIE
T1CON = 0                              ' prescaler=0; timer1=off
TMR1L = 0                              ' clear timer1
TMR1H = 0                              '
CCP1CON = 11                           ' special event trigger enabled
C1 = 49999                             ' load match value = 24499.5us
PIE1.CCP1IE = 1                        ' enable CCP1 interrupt
PIR1.CCP1IF = 0                        ' clear CCP1 interrupt flag
T1CON.TMR1ON = 1                       ' start Timer1
end sub

main:
Initialize

While true                             ' loop forever
If update = true Then               ' advance seconds?
Clock24                          ' yes...then output
End If
Wend

end.

Status
Not open for further replies.