Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

PIC One Second Algorithm from Roman Black

Status
Not open for further replies.

BlackOps

Full Member level 5
Joined
Jan 1, 2005
Messages
279
Helped
14
Reputation
28
Reaction score
3
Trophy points
1,298
Location
AZERBAIJAN
Activity points
2,496
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
addwf bres_lo,f
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?

i'd appreciate your help!
thanks in advance
 

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
    Helpful Answer Positive Rating
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



RESET_ADDR		EQU	0x00		
ISR_ADDR		EQU	0x04 	
COMRAM_ADDR		EQU 0x70	

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


	ORG RESET_ADDR

START
	goto	SETUP    	
	ORG		ISR_ADDR

;***********************************************************************************
; 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

movlw xxxx ;reload count here
movwf count
decfsz count2,f
goto intexit

movlw xxxx ;reload count2 here
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
    Helpful Answer Positive Rating
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 



RESET_ADDR      EQU   0x00       
ISR_ADDR      EQU   0x04     
COMRAM_ADDR      EQU 0x70    

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


   ORG RESET_ADDR 

START 
   goto   SETUP        
   ORG      ISR_ADDR 

;*********************************************************************************** 
; 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
    Helpful Answer Positive Rating
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? :D
 


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
    Helpful Answer Positive Rating
Using the prescaler will certainly introduce accuracy and timing errors. You must maintain 1:1 timer operation to achieve RTC accuracy.
 

VVV, thank you, every your reply helps.


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?

please help me

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 



RESET_ADDR      EQU   0x00        
ISR_ADDR      	EQU   0x04      
COMRAM_ADDR     EQU   0x70    

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


   ORG RESET_ADDR 

START 
   goto   	SETUP        
   ORG      ISR_ADDR 

;***********************************************************************************
; 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
   				   			; TMR0 loading		-	2
   				   		 	; 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
TMR0 preloading - 2 cycles

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
        addwf   TMR0,F          ; reload TMR0 for next interrupt  |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
;
;  reload 1 second counters
;
        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
      TMR0L = TMR0L + T0Load              ' include additional TMR0 counts
      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
   ADCON1 = 15
   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
   ADCON1 = 15
   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
   ADCON1 = 15
   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.

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top