Paul B. Webster VK2BZC

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
  Loop

and 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
  Loop

For 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_time

Further 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 == 0xFF

This 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 code

where 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,F

I'm not sure if this one is more obscure or not:

    movfw   RTCC
    addwf   RTIMEL,F
    skpnc
     incf   RTIMEM,F
    skpz
     incf   RTIMEH,F

On 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,F

But on the 18cxxx parts, tmr0 can be 16bits wide. So there maybe other tricks to try there...

See:

Comments:

See also: