Improving an ISR Timer

I would like to improve a timer I wrote for a Playground article. It's purpose is to trigger an ISR to alternately shutdown MAX7221 chips.

The requirements for this timer are that it must be settable around a mid-range of about 700Hz, and that the ISR can be disabled and re-enabled efficiently.

The timer I currently have, uses TCNT2 in the ISR to reset the counter to a higher starting value.

After reading this post - http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1215675974/0 - it seems that using "TOP" is a better way to go.

So I wrote the code below. But as I don't require PWM, it seems that there is room for improvement with it too.

I have tried using Mode 2 (CTC) rather than the Fast PWM mode, but when I do, the "TOP" no longer works.

The code I'm trying is as follows:

#define ISR_FREQ 174    // Sets the speed of the ISR - LOWER IS FASTER 
// prescaler is /128 - 125,000/ISR_FREQ +1 (i.e 249=500Hz, 175=710Hz)


void setup() {
  // stuff
  setISRtimer();        // set-up the timer
  startISR();
}

void loop() { 
  //stuff
stopISR();
  //stuff
startISR();
}

ISR(TIMER2_OVF_vect) {  
  //stuff to shutdown chips
}  

void setISRtimer(){  
  TCCR2A = 0x23;        // set mode = FAST PWM (can use TOP)
  TCCR2B = 0x0D;        // set prescaler to /128 (125kHz)
  // -- 0x0F=/1024 (15,625Hz), 0xE=/256 (62,500Hz), 0xD=/128 (125kHz), 0x0C=/64 (250kHz) --
  OCR2A = ISR_FREQ;     // set TOP (divisor) - see #define
}

void startISR(){  // called a lot!
  TIMSK2 |= (1<<TOIE2); // set interrupts=enabled
}

void stopISR(){    // called a lot!
  TIMSK2 &= (0<<TOIE2); // set interrupts=disabled
}

Help is appreciated. :slight_smile:

I won't have time to get out the datasheet and figure out the correct timer 2 register values for the CTC mode you would want. In the mean time, could you post the CTC code that doesn't work? You could get by equally well using fast PWM mode, but if you do you should disable the timer 2 PWM outputs (you don't need to have an Arduino pin generating a PWM for this application). I believe this would be accomplished by setting:

TCCR2A = 0x03;

but I'd need to check the datasheet to be sure. Basically you want to set the bits that disconnect OC2A and OC2B if you're going to be using a fast PWM. Still, I think the best solution would be CTC mode so let's see if we can get that working.

  • Ben

OK, I dug into Chapt 17 a little more. Got some ideas - and a headache!
Below are the settings that I am using now (PWM) and what I will try tonight.

CURRENT SETTING:
TCCR2A = 100011 (0x23)
COM2A1 = 0 - for OC2A (using for TOP)
COM2A0 = 0 "
COM2B1 = 1 - for OC2B (Clear on match)
COM2B0 = 0 "
rsvd = 0 0
WGM21 = 1 - for mode
WGM20 = 1 "

TCCR2B = 1101 (0xD)
FOC2A = 0 - N/U
FOC2B = 0 "
rsvd= 0 0
WGM22 = 1 - for mode
CS22 = 1 - for prescale /128 - OK
CS21 = 0 "
CS20 = 1 "
WGM22=1 + WGM21=1 + WGM20=1 = Mode 7 (Fast PWM)

PROPOSED SETTING: (changes in red)
TCCR2A = 100010 (0x22)
COM2A1 = 0 - for OC2A (using for TOP)
COM2A0 = 0 "
COM2B1 = 1 - for OC2B SET TO 0?
COM2B0 = 0 "
rsvd = 0 0
WGM21 = 1 - for mode
WGM20 = 0 " CHANGED TO 0

TCCR2B = 1101 (0x5)
FOC2A = 0 - N/U
FOC2B = 0 "
rsvd= 0 0
WGM22 = 0 - for mode CHANGED TO 0
CS22 = 1 - for prescale /128 - OK
CS21 = 0 "
CS20 = 1 "

WGM22=0 + WGM21=1 + WGM20=0 = Mode 2 (CTC)

I do have a few (hopefully simple) questions though.

  • can I set COM2B1 to zero and still get TOP?
  • In Table 17-8 for Fast PWM it says Update of OCRx at BOTTOM
    but for CTC it says Update of OCRx at Immediate
    Will this be a problem when using TOP?

I'll let you know how it goes.
Thanks.

COM2A1 = 0 - for OC2A (using for TOP)

OC2A is another label for the pin that timer 2 PWM A is output on. Be careful not to confuse this with the OCR2A register, which is used for controlling aspects of the PWM (i.e. duty cycle of PWM A or TOP of PWM B)

COM2B1 = 1 - for OC2B SET TO 0?

Yes, you can set all of the COM2xx bits to zero. All this does is disconnect the timer 2 PWM output pins from the PWM hardware, which will keep you from driving I/O lines you probably have no desire to drive. This should answer your first question.

Since you came up with these values by looking at the datasheet, I won't check them too carefully unless you tell me this doesn't work.

  • In Table 17-8 for Fast PWM it says Update of OCRx at BOTTOM
    but for CTC it says Update of OCRx at Immediate
    Will this be a problem when using TOP?

It could be. When running in PWM mode, the OCR2A register is double-buffered, which means that when you set

OCR2A = something

The actual OCR2A register doesn't receive the new value until the timer is at TOP or BOTTOM (depending on the exact mode). If the update of OCR2A is immediate (as in CTC), you can cause single-period problems by setting it at a bad time. For example, let's say you generally need to use a TOP value of 25 to achieve the frequency you want. Furthermore, let's say you want to increase the frequency slightly so you attempt to change your TOP to 20 by setting OCR2A = 20. If the timer 2 counter is greater than 20 when you make this change, it will never see a compare match with OCR2A and your timer will count all the way up to 255 before overflowing back to 0. The result will be a one-time gap between timer 2 interrupts. If you can live with something like this, then it's not a problem. If you can't, you need to actively check the value of the timer 2 counter before you decrease the TOP value and force a compare match if necessary.

The datasheet explains this as:

However, changing TOP to a value close to BOTTOM when the counter is running
with none or a low prescaler value must be done with care since the CTC mode does not
have the double buffering feature. If the new value written to OCR2A is lower than the current
value of TCNT2, the counter will miss the compare match. The counter will then have to count to
its maximum value (0xFF) and wrap around starting at 0x00 before the compare match can
occur.

You should also note that in CTC mode, timer 2 does not generate overflow interrupts. Instead, it generates compare match A interrupts when the counter equals OCR2A (which is your TOP). You want to use the ISR that will be triggered by the OCF2A flag. This is probably why your PWM code did not work when you tried CTC mode.

  • Ben

Ben,

I've made some progress. :slight_smile:
Yes, I was confused about what was a pin and what a register - and where the timer ends and PWM begins. I read section 17 from the beginning, and that helped too. All those names are so similar!

So I removed the PWM settings and all is well.

Then I tried to change from Mode 7 to Mode 2 - no luck, though the ISR seems to have triggered once.
(I was able to go to Mode 3 - still Fast PWM though.)

Then your last paragraph made sense. I was using ISR(TIMER2_OVF_vect).
I Googled and found ISR(TIMER2_COMP_vect).
When I tried it, it didn't even trigger once.

More Google turned up this post . . .
http://www.mail-archive.com/avr-libc-dev@nongnu.org/msg02381.html

It's very recent and describes a bug and a work around using this line:
ISR_ALIAS(__vector_default, TIMER2_COMP_vect);

I got a compile error when I tried it, but I really don't know how or where it's used.

I'm still plugging away, and again, thanks for your great responses.

[edit]So at this point I'm using TCCR2A = 0x03 and TCCR2B = 0x05.
Reading the responses to that post further, the whole subject might be a red herring.
While a later post used ISR(_VECTOR(8)), I sensed the posts were not for the ATMega168.
[/edit]

Here's a hint for finding out what argument to supply to the ISR() macro: look in the WinAVR header file iomx8.h. This file is located in the directory:

arduino-0011\hardware\tools\avr\avr\include\avr

(I know, that's a pretty confusing path).

When you (or, in this case, the Arduino environment) include avr/io.h in your sketch, iomx8.h is automatically included if your device is set to ATmega168. This is the file that has all of the mega48/88/168-specific defines and macros, including names for all of the interrupt vectors. Near the bottom of the file you will find the following:

/* Interrupt vectors */

/* External Interrupt Request 0 */
#define INT0_vect                  _VECTOR(1)
#define SIG_INTERRUPT0                  _VECTOR(1)

/* External Interrupt Request 1 */
#define INT1_vect                  _VECTOR(2)
#define SIG_INTERRUPT1                  _VECTOR(2)

/* Pin Change Interrupt Request 0 */
#define PCINT0_vect                  _VECTOR(3)
#define SIG_PIN_CHANGE0                  _VECTOR(3)

/* Pin Change Interrupt Request 0 */
#define PCINT1_vect                  _VECTOR(4)
#define SIG_PIN_CHANGE1                  _VECTOR(4)

/* Pin Change Interrupt Request 1 */
#define PCINT2_vect                  _VECTOR(5)
#define SIG_PIN_CHANGE2                  _VECTOR(5)

/* Watchdog Time-out Interrupt */
#define WDT_vect                  _VECTOR(6)
#define SIG_WATCHDOG_TIMEOUT            _VECTOR(6)

/* Timer/Counter2 Compare Match A */
#define TIMER2_COMPA_vect            _VECTOR(7)
#define SIG_OUTPUT_COMPARE2A            _VECTOR(7)

/* Timer/Counter2 Compare Match A */
#define TIMER2_COMPB_vect            _VECTOR(8)
#define SIG_OUTPUT_COMPARE2B            _VECTOR(8)

/* Timer/Counter2 Overflow */
#define TIMER2_OVF_vect                  _VECTOR(9)
#define SIG_OVERFLOW2                  _VECTOR(9)

/* Timer/Counter1 Capture Event */
#define TIMER1_CAPT_vect            _VECTOR(10)
#define SIG_INPUT_CAPTURE1            _VECTOR(10)

/* Timer/Counter1 Compare Match A */
#define TIMER1_COMPA_vect            _VECTOR(11)
#define SIG_OUTPUT_COMPARE1A            _VECTOR(11)

/* Timer/Counter1 Compare Match B */
#define TIMER1_COMPB_vect            _VECTOR(12)
#define SIG_OUTPUT_COMPARE1B            _VECTOR(12)

/* Timer/Counter1 Overflow */
#define TIMER1_OVF_vect                  _VECTOR(13)
#define SIG_OVERFLOW1                  _VECTOR(13)

/* TimerCounter0 Compare Match A */
#define TIMER0_COMPA_vect            _VECTOR(14)
#define SIG_OUTPUT_COMPARE0A            _VECTOR(14)

/* TimerCounter0 Compare Match B */
#define TIMER0_COMPB_vect            _VECTOR(15)
#define SIG_OUTPUT_COMPARE0B            _VECTOR(15)

/* Timer/Couner0 Overflow */
#define TIMER0_OVF_vect                  _VECTOR(16)
#define SIG_OVERFLOW0                  _VECTOR(16)

/* SPI Serial Transfer Complete */
#define SPI_STC_vect                  _VECTOR(17)
#define SIG_SPI                        _VECTOR(17)

/* USART Rx Complete */
#define USART_RX_vect                  _VECTOR(18)
#define SIG_USART_RECV                  _VECTOR(18)

/* USART, Data Register Empty */
#define USART_UDRE_vect                  _VECTOR(19)
#define SIG_USART_DATA                  _VECTOR(19)

/* USART Tx Complete */
#define USART_TX_vect                  _VECTOR(20)
#define SIG_USART_TRANS                  _VECTOR(20)

/* ADC Conversion Complete */
#define ADC_vect                  _VECTOR(21)
#define SIG_ADC                        _VECTOR(21)

/* EEPROM Ready */
#define EE_READY_vect                  _VECTOR(22)
#define SIG_EEPROM_READY            _VECTOR(22)

/* Analog Comparator */
#define ANALOG_COMP_vect            _VECTOR(23)
#define SIG_COMPARATOR                  _VECTOR(23)

/* Two-wire Serial Interface */
#define TWI_vect                  _VECTOR(24)
#define SIG_TWI                        _VECTOR(24)
#define SIG_2WIRE_SERIAL            _VECTOR(24)

/* Store Program Memory Read */
#define SPM_READY_vect                  _VECTOR(25)
#define SIG_SPM_READY                  _VECTOR(25)

To make CTC mode work, you are looking for a compare match with OCR2A, which means you are looking to use the interrupt:

ISR(TIMER2_COMPA_vect)
{

}

This interrupt is triggered every time the OCF2A flag is set (which happens every time the timer counter TCNT2 equals your compare register OCR2A) if you have the compare-match A interrupt enabled on timer 2. Please let me know if this helps you get things working or if you have any questions!

  • Ben

Finally got it. :slight_smile:
My problem was not only the name of the ISR, but the fact that I was still using TOIE2 instead of OCIE2A to enable interrupts. ::slight_smile:

I'm happy I made the switch from Mode0 - set by changing TCNT2, to Mode2 - set by TOP. Not only is it easier to tweak, but for some reason, the time spent in the ISR seems to have decreased from 490us to 350us.

This is what I ended up with . . .

#define ISR_FREQ 170     //740Hz        // Sets the speed of the ISR - LOWER IS FASTER 

ISR(TIMER2_COMPA_vect) {  //Called when ISR triggers
  //stuff
}  

void setISRtimer(){  // setup ISR timer 
  TCCR2A = (1<<WGM21);                  // WGM22=0 + WGM21=1 + WGM20=0 = Mode2 (CTC)
  TCCR2B = (1<<CS22)|(0<<CS21)|(1<<CS20);  // set prescaler to /128(125kHz)
  TCNT2 = 0;                                      //clear counter
  OCR2A = ISR_FREQ;                        // set TOP (divisor) - see #define
}

void startISR(){  // Starts the ISR
  TIMSK2=(1<<OCIE2A);                     // set interrupts=enabled (calls ISR(TIMER2_COMPA_vect)
}

void stopISR(){    // Stops the ISR
  TIMSK2=(0<<OCIE2A);                    // disable interrupts
}

Ben - thanks again for all of your help and support.
John :-X

TIMSK2=(0<<OCIE2A); // disable interrupts

I'm very glad to hear you have things working! One thing I noticed was the above line. While this will technically work in this instance, you should note that this is not the correct way to clear a bit. 0 << x is always 0, so your line above is exactly the same as:

TIMSK2 = 0;

This will disable all timer 2 interrupts, not just the compare match A interrupt. Bitshifting 0 is the same as multiplying or dividing 0 by some number, which is pointless if you know ahead of time that you're dealing with a 0. If you want a more targeted interrupt disable, you would need to use:

TIMSK2 &= ~(1 << OCIE2A);

which only clears the OCIE2A bit of TIMSK2 rather than clearing all bits as the first line does.

  • Ben

Cool, I was wondering about that. I saw some examples with that.

Should I use . . .
TIMSK2 |= (1<<OCIE2A); // to set the bit?
and
TIMSK2 &= (0<<OCIE2A); // to clear the bit?

I guess TIMSK2 &= ~(1 << OCIE2A) is the same as TIMSK2 &= (0<<OCIE2A) , right?

Good eye!

I guess TIMSK2 &= ~(1 << OCIE2A) is the same as TIMSK2 &= (0<<OCIE2A) , right?

Nope! If you ever find yourself writing 0 << x, you are doing something wrong! As I was trying to explain in my previous post, 0 << x is always 0. I'll try to explain why this is in two different ways:

  1. Note that n << x is the same as n * 2 ^ x. This is a fancy way of expressing that every time you bit-shift a number left by one, you are doubling it. So what happens to the expression

n * 2 ^ x

when n = 0? Well, the result will be 0 no matter what x is.

  1. Let's look at what bitshifting does to some binary numbers.

00101101 << 3 = 01101000
00000000 << 3 = 00000000

In the first example, bit-shifting quite clearly has an effect. In the second example, however, it's obvious how futile it is to attempt to bit-shift the number, right?

So now let's look at what you're trying to accomplish and what the two formulations do. Your goal is to clear the OCIE2A bit of the TIMSK2 byte. Without loss of generality, let's arbitrarily say the the OCIE2A bit is bit 5, and let's say that TIMSK2 has the value 00101100.

Your approach:

TIMSK2 &= (0 << OCIE2A)
=> TIMSK2 = TIMSK2 & (0 << OCIE2A)
=> TIMSK2 = 0b00101100 & (0 << 5)
=> TIMSK2 = 0b00101100 & 0b00000000
=> TIMSK2 = 0 since x & 0 is always 0.

My approach:

TIMSK2 &= ~(1 << OCIE2A)
=> TIMSK2 = 0b00101100 & ~(1 << 5)
=> TIMSK2 = 0b00101100 & ~(0b00100000)
=> TIMSK2 = 0b00101100 & 0b11011111
=> TIMSK2 = 0b00001100

This time, the bitwise AND only clears bit 5 of TIMSK2 since this is the only part of our bitmask that is zero. You can see that in these two cases the results are different, and the second one is the one we want if our goal is to clear the OCIE2A bit while leaving the rest of TIMSK2 unchanged. When doing bitwise math, the following operations are the ones you will find yourself using over and over again:

Setting a bit: byte |= 1 << bit;
Clearing a bit: byte &= ~(1 << bit);
Toggling a bit: byte ^= 1 << bit;
Checking if a bit is set: if (byte & (1 << bit))
Checking if a bit is cleared: if (~byte & (1 << bit)) OR if (!(byte & (1 << bit)))

I'm not sure how good a job I'm doing at explaining this; does what I'm saying make sense?

  • Ben