Timer Tangles - Resetting the TOFlag for X2 Interrupts

#1
I had developed a program that uses the internal timer of an X2 PICAXE to read an array of Touch pads. The timer was set to cause an interrupt every 100mS, allowing the Touch pads to be read about 10 times a second.

Some of the foreground tasks either shared some variables and flags with the interrupt routine or were time critical. This made it necessary to disable interrupts in the foreground for critical but short periods; usually in response to a Touch event.

The problem

Periodically, I found that the regular timer interrupts would stop but then, about 11 minutes later, would mysteriously start again.

The code that I was using was some that I had copied from the forum some years ago:

Code:
Interrupt:                                  'This code can be problematic, so don't use it!
          <application specific code goes here>
          Timer = $FFFF                     'Reset timer0 to interrupt on 1st major tick
          TOFlag = False                    'Clear the flag
          SetIntFlags %10000000, %10000000  'Set timer 0 to cause interrupts
          Return
My first lightbulb moment came when I realised that 11 minutes is (about) 65,536 x 100mS! Hmm. The Timer value was somehow ticking over from 65,535 ($FFFF) to 0 without causing an interrupt.

How the timer works in the X2

The whole timer consists of 2 x 16-bit registers. The first 16-bit counter is preloaded with the value specified in the SetTimer command. Every time it rolls over from 65,535 to 0 it increments the second 16-bit register. The first register is then reloaded with the SetTimer value and continues to count up again. The second 16-bit register is actually the Timer variable and can be set with 'Timer = value'. When the Timer variable ticks over from 65,535 to 0, it sets the TOFlag bit to 1, which can be configured to cause an interrupt. When using interrupts, more often than not the Timer variable is set to 65,535 so that it triggers an interrupt every time the first timer rolls over to 0. For a periodical timer like I wanted, the Timer value must be reset (preset) to 65,535 on every interrupt, in preparation for the next timer interrupt.

What was going wrong

Then I realised that the timer runs in the background, regardless of (almost) everything else that happens in the PICAXE code, be it in the interrupt routine or elsewhere. If, in the interrupt routine, the Timer variable is set to 65,535 just before the first counter rolls over to 0, there may not be enough time to clear the TOFlag before the rollover event occurred. This would mean that the Timer variable would have to increment another 65,535 times before the TOFlag is set and another interrupt occurred. Hence the 11 minute break between timer interrupts.

So the TOFlag should be cleared before the Timer value is set; especially when the Timer is being set to 65,535 ($FFFF).

The revised code

The change in code is critical but quite subtle. I have included a more complete piece of code to show how the timer interrupt is configered for a regular 100mS timer.
Code:
#PICAXE 28X2         ' or 20X2, 28X1, 40X1, 40X2
'
' **** Constants ****
'
Symbol False            = 0
Symbol True             = 1
Symbol mskBGTimer       = %10000000 'Timer 0 is bit 7 
Symbol flgBGTimer       = %10000000 'Timer 0 is bit 7
Symbol tmrIntOn1stTick  = 65535     'Interrupt to be caused by roll over on first major tick
Symbol tmr100mS_8       = 62411     '(for 8MHz) = 65536 - (Treq * 1,000,000 / Clk / 256)
'
Init:     'PICAXE running at the default speed (8MHz)
          'Start the background timer (runs continuously)
          TOFlag = False
          Timer = tmrIntOn1stTick               '
          SetTimer tmr100mS_8                   'Expires after (every) 100 milliseconds
          SetIntFlags flgBGTimer, mskBGTimer    'Set timer 0 to interrupt
          '
MainLoop: Do
             '<your main loop code
             '           goes here>
          Loop
          '
Interrupt:'<your interrupt code
          '           goes here>
          TOFlag = False                        'Reset (clear) the flag first
          Timer = tmrIntOn1stTick               'Then reset the timer
          '
          SetIntFlags flgBGTimer, mskBGTimer    'Configure timer 0 to interrupt
          Return                                'Return enables interrupts again
 

hippy

Technical Support
Staff member
#2
In addition, it would be advisable to clear the 'toFlag' interrupt flag and reset the timer at the start of the interrupt routine rather than at the end.
 
#3
I have to agree that the commands to refresh the timer configuration for regular timer interrupts are best placed at the beginning of the interrupt routine.

In my particular case it did not matter if a single timer tick was missed, since it would only occur after a touch input was being processed. It was more important that the timer interrupts remained enabled.
 

PhilHornby

Senior Member
#4
Beginning or End?...

I have to agree that the commands to refresh the timer configuration for regular timer interrupts are best placed at the beginning of the interrupt routine.
Doesn't this introduce the danger of becoming trapped in the Interrupt Service Routine?

I'm thinking of the scenario of a very short Timer Interval, combined with a lengthy ISR and/or slow clock frequency...
...if T0_FLAG is cleared at the beginning of the routine, it might have already become Set again by the time the return is executed - thus returning program control straight back to the ISR.

Of course, if the ISR was always too lengthy, you'd find it in testing ... but if it was an occasional condition, it could get interesting!
 
#5
Doesn't this introduce the danger of becoming trapped in the Interrupt Service Routine?

I'm thinking of the scenario of a very short Timer Interval, combined with a lengthy ISR and/or slow clock frequency...
...if T0_FLAG is cleared at the beginning of the routine, it might have already become Set again by the time the return is executed - thus returning program control straight back to the ISR.

Of course, if the ISR was always too lengthy, you'd find it in testing ... but if it was an occasional condition, it could get interesting!
That's when the age-old rules for interrupt service routines apply: (1) Keep it short and simple - Ie no pauses or loops. And (2) keep the interrupts far enough apart so that they can be serviced and your main program has time do what it needs to do.

Finally, use a technology suited to the task. I have used 5mS interrupts on a bare PIC but would not dare to do that on a PICAXE!
 
Top