Atmega328p timers, is this use correct?

I'm trying to have an interrupt that runs at a particular interval, regardless of how long the main loop takes to run. The purpose of this interrupt is to check at a regular interval whether a condition has been met, and if so activate a pn change interrupt to be able to run when (at some future time) a pin change occurs. Once the timer interrupt has fired in circumstances where the condition has been met I want to turn the timer off again so I don't get further calls of the timer interrupt and don't waste power/processing capacity/... servicing a timer and associated interrupt which are no lonegr needed (until some much more distant future time, when I turn the timer back on when other conditions are met).

I do NOT want the timer doing any automaticpin toggling for PWMing an output wire.

Is the use of code shown below correct?
I read over the gammon article on timers and the one on interrupts, but while there were examples Icouldn't see much about explicitly starting and stopping the timers.

I also read over the 328p's datasheet, about the registers involved, but it only gave examples of tiny snippets fo code writing to the registrs, not of actually starting and stopping the timers. It also warned of:
a) some special conditionsneeded whenever accessing a 16 bit (2 byte) register, I'mnot sure if GCC handles these automatically or not?
b) some cases in which when OCR1A gets set the timer's counted value may have already ascended above it, and one wouldn't get another timer interrupt for absolutely ages (I trigger at 250 in CTC mode, if this happened it would go up to >65K before relooping and triggering at 250).

Is my code proof against those risks?

Please note that the timer starting function always gets called, in my use of it, from inside a different ISR, so interrupts are disabled at the time it is set up.

Code below, thank you.
Please "scroll" to the side in the code to see my full comments which further point out my questions.

void StartTimer(){ //this gets called within an ISR which is not triggered by the timer
  TCCR1A = 0;          // apparently this is how to start the timer
  TCCR1B = bit(WGM12) | bit(CS11);   // supposedly sets CTC mode and selects a pre-scaler
  OCR1A =  250;       // derived by trial and error to set how long I want to wait from the timer starting until the timer driven interrupt fires, it is written like this and 250 is very much a constant, I could do it by #define or by a const uint8_t
  TIMSK1 = bit (OCIE1A);             //sets up an interrupt for when the timer's internal value reaches the OCR1A value and resets?
}

ISR (TIMER1_COMPA_vect){
  //do stuff to assess what value "situation" has
  /*
  stuff
  */
  if( (situation==1 ) && (PCICR == 0)){ //situation met, and pin change interrupt is off
    //do various stuff here
    /*
    stuff
    */
    PCIFR  |= bit (PCIF0); //clears pin change interrupt's flag, thereby forgets about any which have already occured but not been serviced
    PCICR  |= bit (PCIE0);//re-enables pin change interrupt, I know these pin change interrupt register writing actions work properly
    TCCR1B = 0; //I think this stops the timer? Does it indeed, or does it just stop the interrupt? Does this stop the timer? and does it reset the timer's count to zero? I tested by adding some port manipulation in this ISR for debug purposes and watching on an oscilloscope, it certainly looked like the ISR stopped re-occuring once the situation condition was met
  }
}

ISR (PCINT0_vect ){
  //do various stuff
  /*
  stuff
  */
  PCICR&=0b11111110; //this disables the pin change interrupt, I know this works
  StartTimer(); //will this timer, started like this always start the timer at zero? Or could it start from any arbitrary value depending on where it got to last time it was stopped? Can it end up starting from values >OCR1A, which would be a serious problem as that way the pin change interrupt wouldn't get checked for whether to re-enable for far too long an interval
}

Or should I perhaps take the OCR1A out of the StartTimer(); function, and set OCR1A only once, during the program's initial setup? Should I be accessing any other registers to reset the timer 1's timer variable somehow at the time I try to stop the timer?
Thanks

Yes, sure. You don't need to change any other registers if you need just to stop it

Let the timer interrupt occur at the desired rate and only turn its interrupt off when done.

Did you understand the timer modes?

"Let the timer interrupt occur at the desired rate and only turn its interrupt off when done."
Is my code doing that, I'm not entirely sure if the timer does turn off?

I'm not sure I have fully understood the timer modes, is CTC indeed the best for causing timer driven interrupts at a particular interval? And being able to turn the timer on and off?

Let the timer continue running. Only disable the interrupt.

All timer modes can trigger interrupts at regular intervals.

"Let the timer continue running. Only disable the interrupt."
What advantage does this have over disabling the timer entirely?

"All timer modes can trigger interrupts at regular intervals"
How do I work out which mode is best to pick here?
Thanks

Any further tips?
Thanks

It's hard to give an exact answer without seeing the big picture.
Have you tested your sketch? Is it working as you expected?

After all I think CTC mode will do the job for you, but I would definitely reset the counter every time before the timer is started.

void StartTimer(){ 
  TCCR1A = 0; 
  TCCR1B = bit(WGM12);          //CTC mode
  OCR1A =  250;                 //set TOP value
  TIMSK1 = bit (OCIE1A);        //Output Compare A Interrupt Enable
  TCNT1 = 0;                    //RESET counter
  TCCR1B |= bit(CS11);          //start the timer
}

Thanks. As far as testing goes, all my testing is able to do is test whether, and when the interrupt runs, I'm not aware how one could monitor whether the timer itself is running. In terms of behaviour as seen on serial.print statements and port toggling read by an oscilloscope it now appears correct though.

I think I've understood though that TCCR1B can be used to stop the timer by zeroing all the pre-scaler related bits, and that the instant you set a pre-scaler is the moment the timer will start. With this in mind it seems I am ok to do anything to TCNT1 and OCR1A AFTER having zeroed TCCR1B's pre-scaler bits and BEFORE setting those pre-scaler bits back to the desired value.

My code now starts the timer, each time it is required to be started, using:

TCCR1A = 0;
TCCR1B =0;
TCCR1B = bit(WGM12);
TCNT1 = 0;
OCR1A =  250;
TIMSK1 = bit (OCIE1A);
TCCR1B |= bit(CS11);

And then when the interrupt runs and finds the right conditions met it does:
TCCR1B = 0;

Oscilloscope readings certainly showed that with this setup the timer driven interrupt always occured a consistent time after the timer was started (which had not been the case before I'd included the TCNT1=0; line) and that the interrupt stopped happening after an instance of the interrupt happening once the conditions had been met (which didn't happen if I commented out the TCCR1B=0; line from within the interrupt's if loop).

It seems this is solved then, so long as the special things the atmega datasheet says about access order to the two byte registers are handled by the compiler.

That should be enough.

Yes. As long as Clock Select Bits are zero, the Timer/Counter is stopped.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.