# Irregularly triggering an ISR using timers

I seek to call an ISR routine after a fixed time of 400us. This action needs to be done every now and then (in the range of 0.8-3.0ms). For that matter, I thought I'd set timer2 to CTC mode, use OCR2A to set some TOP value for 400us and let the timer call the ISR routine after 400us. Within the ISR I would then stop the timer from triggering interrupts, until I need to count to 400us again, where I would activate the timer interrupt again.

This works, as expected, except for the fact, that after activating the interrupt for OCR2A again, it instantly triggers. The code below, produces a 1ms alternating signal, instead of 1ms+400us.

My question is, am I on a good way with this, or should I use the overflow ISR and preload the timer? Or any other method? The problem is, that after the 1ms waiting time in the main loop, I'd expect the counter to count from 0 to 100, meaning 400us. However on an oscilloscope I see the pin toggle each 1ms.

uint32_t timestamp;

void setup() {
Serial.begin(115200);
pinMode(9, OUTPUT);
cli();         // disable global interrupts
// Timer2 for counting (to release the pin)
TCCR2A |= (1 << WGM21); // CTC mode, clear timer on compare match
TCCR2B |= (1 << CS22); // Prescalar 64

OCR2A = 99; // ticks, before ISR is called
TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt
TCNT2 = 0x00; // set the timer to 0
// counting time[s] = (OCR+1)/(F_cpu/prescalar)
// 99 = 400us | 124 = 500us
sei(); // enable global interrups
timestamp = micros();
}

void loop() {
uint32_t now = micros();
if (now - timestamp > 1000) {
timestamp = now;
cli();
TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt
sei();
TCNT2 = 0x00;
}
}

ISR(TIMER2_COMPA_vect) {
// Serial.print(F("Here\n"));
PORTB ^= 2; // set pin 9 high again
// TCNT2 = 0x00; // reset timer for testing
// deactivate the counter
cli();
TIMSK2 = 0x00; // unlink the ISR from timer
sei();
}


However on an oscilloscope I see the pin toggle each 1ms.

That's what you expect. You set up the timer to fire every 1 ms. Since the ISR is called some time after that (and that is what toggles the pin) you will see the pin toggle every 1 ms.

I've reworked your example a bit to make it clearer, and to toggle the pin when we start the timer, and again when the ISR fires:

uint32_t timestamp;

void setup()
{
pinMode(9, OUTPUT);
// Timer2 for counting
TCCR2A = bit (WGM21); // CTC mode, clear timer on compare match
OCR2A = 99;           // ticks, before ISR is called

// counting time[s] = (OCR+1)/(F_cpu/prescalar)
// 99 = 400us | 124 = 500us
timestamp = micros();
}  // end of seupt

void loop()
{
uint32_t now = micros();
if (now - timestamp >= 1000)
{
timestamp = now;
TIFR2 = bit (OCF2A);   // clear any pending interrupt
TCNT2 = 0;             // make sure counter is zero
TIMSK2 = bit (OCIE2A); // enable timer compare interrupt
TCCR2B = bit (CS22);   // start timer: Prescalar 64
PINB = bit (1);        // toggle pin 9
}  // end of if
}  // end of loop

ISR(TIMER2_COMPA_vect)
{
PINB = bit (1); // toggle pin 9
TCCR2B = 0;     // stop timer
}  // end of TIMER2_COMPA_vect


You can see from the scope output that the timer is started every 1 ms (as expected) because Cursor 1 is at -1 ms. The width of the pulse is 400 µs, as expected.

In my example I do not have to set so many settings each time I want to start the timer.

I took out the cli and sei so I saved you two lines. Don't forget to count them!

If you leave the timer running, it may well have reached the counter again (even though not generating an interrupt). Thus this line is important (even more so for you):

TIFR2 = bit (OCF2A);   // clear any pending interrupt


Also, if you leave the timer running, you may have a race condition. Say the timer is just about to tick over and generate the interrupt. You clear the flag (as shown above), but one machine cycle later the timer reaches the counter and sets it again, just before you get a chance to zero it. With the timer running, there could be combinations of when you enable interrupts, zero the flag, and clear the pending interrupt flag, that could occasionally work against you. Having the timer stopped is much safer.

Now if you are worried about the number of lines you need to start it (I wouldn't be) then you can move this line into setup:

TIMSK2 = bit (OCIE2A); // enable timer compare interrupt


Since you stop the timer in the ISR it doesn't matter if the interrupt is still enabled. Now you just have three lines to start the timer:

TIFR2 = bit (OCF2A);  // clear any pending interrupt
TCNT2 = 0;            // make sure timer is zeroed
TCCR2B = bit (CS22);  // start timer: Prescalar 64


I think we've agreed that clearing the compare flag is important, so this isn't any lengthier.

looking at your divisioning of the time axis, and from code, it is clear, that there is a 600us break, not 1ms!

Yes, there's a 600 µs break, because you start the timer every 1 ms, and it runs for 400 µs, thus there will be a 600 µs break.

I seek to call an ISR routine after a fixed time of 400us.

That's exactly what my posted code does. The pin goes high at the time you desire, in loop, and it goes low 400 µs later. That's what you wanted: a 400 µs interval.

• As far as I can tell, the only additional thing is TIFR2 = bit (OCF2A);. In your code, you stop the timer alltogether. Isn't it cleaner the way I did, where I just disconnect the timer from the interrupt? In my example I do not have to set so many settings each time I want to start the timer. Or is it even desirable to use the overflow timer instead,... would that result in less hastle when starting the timer? – mike Mar 29 '16 at 6:48
• btw.: looking at your divisioning of the time axis, and from code, it is clear, that there is a 600us break, not 1ms! Since both timers (timer0 running micros() and timer2) run parallel, it is clear, that the break is 1ms-400us = 600us. – mike Mar 29 '16 at 7:54
• See amended answer. – Nick Gammon Mar 29 '16 at 20:45
• Thanks for the additional explanation. I was originally worried about the flag, that was set on compare match, but now I totally agree, that stopping the timer is what I was looking for! – mike Mar 31 '16 at 12:11