There are *THREE* ways to achieve this functionality on a PIC with timer and interrupts, *two* on one with timer and no interrupts (16C5xx, 12c5xx). The three ways are: 1} Isosynchronous code - loops performing all functions take a measured time to cycle. Very tedious, virtually impossible in "C".2} Interrupt-driven. On interrupt, counters are incremented/ decremented and overflow to other counters. The main routine can "peek" at these counters and determine when to perform the necessary task which it does outside of "interrupt time", thus avoiding problems with interrupts overlapping.
To resolve whether the task has been performed at each required interval however, the interrupt routine can set a (series of) flag(s) on certain overflows e.g., each hour. The mainline code then checks if the flag is set *and* the hour is right, if so clears the flag (so it doesn't perform the task twice) and performs the action.
Hint: These flags are all bit flags within a byte; the interrupt routine sets the whole byte. Easier if worked the other way - it clears all the bits at once.
Along this line, it simplifies things a bit if the interrupt routine only deals with say, seconds, setting a flag each second which the main routine uses to advance the second count and count minutes, hours, days etc. But this does lead to the next proposal:
3} Timer-polling. This is the simplest and easiest to code, and can be done on the 12-bit core devices without interrupt hardware. That makes it a very *mature* approach!
The basis is to set up the timer and prescaler so that the timer overflows every so many thousand clock cycles, more than enough to perform either the whole of the "other" functions, or a substantial module of those functions. After performing a function group known to take such a time, you wait polling for the overflow, perform the timing count when it happens and go on to the next function group. Thus for one function:
While TRUE Do main_function wait_for_rollover timer_count Loopand for multiple parts of code each taking less *on average* than the overflow time:
While TRUE Do main_function1 wait_for_rollover timer_count main_function2 wait_for_rollover timer_count main_function3 wait_for_rollover timer_count main_function4 wait_for_rollover timer_count LoopFor example, function 1 can take 1½ times the overflow time while function 2 is very short, the first overflow is serviced before another overflow occurs, and the short second function brings the next overflow in on time. You can even cut it slightly finer than that.
The "rollover" code (should be a subroutine) on the devices with an interrupt flag (T0IF) consists of polling for that flag *and clearing it when found*, but note: no interrupt, specific or global is enabled. (Other interrupts can however be enabled as long as they will not make the mainline code modules run significantly over-time as above.)
The timing cycles are each of the form:
timer macro unit,unit_max,end_time decfsz unit goto end_time movlw unit_max movwf unit endm ;e.g., for seconds, minutes decfsz seconds goto done_time movlw 60 movwf seconds decfsz minutes goto done_time movlw 60 movwf minutes ;could be macro coded as: timer seconds,60,done_time timer minutes,60,done_time ; ..more timers.. ; ..payload; i.e. what you do at the end.. done_timeFurther hint on using interrupts and timer in general: IMNSHO don't even *think* of setting/ re-setting/ altering the timer value within the interrupt or poll routine. Always let it count un-impeded, use "nice" numbers. *Far* less heartache that way. KISS!
Øyvind Kaurstad was having a problem with jitter in his timing routine and said:
Immediately after I read TMR0 I also clear it, and this will prevent a new overflow for some time.
But Nikolai warns:
There is a slim chance that TMR0 overflows in the same instruction when you read it. As I understand from datasheets, read is made in the beginning of instruction cycle, and write - in the end. So actually, there is still some room for error. I don't know if this can matter. Best way is compare with this (INTF part of your routine):
Øyvind Kaurstad responded:
I actually discovered this myself yesterday. If I read 0xFF from TMR0 the INTF flag is already set, and my check of the INTF-flag will actually generate a big jitter.I solved it by checking if my TIMER_STR (copy of TMR0) is 0xFF. If it is, I just skip the INTF-test.
Like this:
movf TMR0,0 ;Read TMR0 clrf TMR0 movwf TIMER_STR ;Store it in TIMER_STR comf TIMER_STR,0 ;Complementing it, store in W skpnz ;If it was 0xFF the Z flag will be set goto $+3 ;and the goto will be performed btfss INTCON,T0IF ;These two instructions will be skipped incf TIMER_STR+1,1 ;only if TIMER_STR == 0xFFThis solved my problem! Thanks for all help.
William J. Kitchen says
Make a subroutine that adds the contents of the RTCC to several registers then resets the RTCC. The subroutine has to be called often enough to keep the RTCC from overflowing. This means including a call to it inside all loops. The tricky part is accounting for the time it takes for the subroutine to execute. Here's an example that creates a 24 bit timer:TIMER ; add RTCC contents to 24 bit value (RTIMEL, RTIMEM, RTIMEH) ; This strange looking addition is designed to execute in exactly ; the same number of cycles regardless of the number of carries. movfw RTCC ; addwf RTIMEL,F ; btfss STATUS,C ; goto $+2 ; incfsz RTIMEM,F ; goto $+2 ; incf RTIMEH,F ; ;* reinitialize RTCC movlw $+3-TIMER ; this the number of instruction cycles between ; the time that the RTCC is read and the time when ; it is written, plus 2 more to adjust for the ; time that is required for the RTCC to restart ; after being written movwf RTCC ; ;* return retlw 0 ;This routine will always take exactly the same time to execute.
Unfortunately, using a prescaler with this technique will introduce error because subroutine can be called at varying times relative to the prescaler's interval.
I've used this with great success to generate long and accurate timers on PICS without interrupts. It can also be used to get very high resolution (as fine as 0.2 microsecond with a 20mhz pic) measurement of external events on PICS that do have interrupts, while still maintaining long counts.
Scott Dattalo says:
A slightly easier to read (IMO) and slightly faster, yet still isochronous is:movfw RTCC addwf RTIMEL,F rlf known_zero,W addwf RTIMEM,F rlf known_zero,W addwf RTIMEH,F ;followed by the RTCC fix-up codewhere known_zero is a variable that has been initialized to zero.
A slighty more obscure version that doesn't require the known_zero:
incf RTIMEH,F movfw RTCC addwf RTIMEL,F skpnc incfsz RTIMEM,F decf RTIMEH,FI'm not sure if this one is more obscure or not:
movfw RTCC addwf RTIMEL,F skpnc incf RTIMEM,F skpz incf RTIMEH,FOn the 18cxxx you could do this:
movf RTCC,w ;or whatever the tmr register is called... addwf RTIMEL,F clrf wreg addwfc RTIMEM,F addwfc RTIMEH,FBut on the 18cxxx parts, tmr0 can be 16bits wide. So there maybe other tricks to try there...
See:
Comments:
See also:
High precision timed events without interrupts for 12-bit PIC (instruction cycle accurate) using a non-isochronous main loopFor a measurement device I needed exact (instruction cycle accurate) timed intervals. In this program code, in every period, the main code executes first, which may take any number of instructions (that will fit in the period time window, please see code). Then the program will wait for the tmr0 value to reach 255. The program then knows when the tmr0 rollover is about to happen and synchronizes on that rollover to reach instruction cycle precision. There is also a version that will work everywhere in the program memory: