Timer1 Interrupt

Hey everyone,

I'm trying to get interrupts to occur at a very fast frequency using timer1. Ideally I need 5,000,000 interrupts a second.

First question - this is possible...right? Arduino uses 16 MHz clock, so it should be, correct?

Right now I'm having problems getting timer1 to work correctly. I don't know what I'm doing wrong. I've got the code below. I hook my uno up to my ossciliscope, to find I can't generate a square wave faster than 40 KHz no matter what I do (or interrupts at 80 KHz). The code below will generate a square wave of about 37 KHz according to my o-scope, but it should be 100 KHz.

Any help would be greatly appreciated!

void setup() {
  
  pinMode(9, OUTPUT);
  
  // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 159;              // compare match register - 100 KHz 
  TCCR1B = (1 << WGM12);    // CTC mode
  TCCR1B |= (1 << CS10);    // 1 prescaler 
  TIMSK1 = (1 << OCIE1A);   // enable timer compare interrupt
  interrupts();             // enable all interrupts
}

// timer compare interrupt service routine
ISR(TIMER1_COMPA_vect) {       
  digitalWrite(9, digitalRead(9) ^ 1);
}

void loop()
{
  // your program here...
}

Hello,

tmmurcko:
I'm trying to get interrupts to occur at a very fast frequency using timer1. Ideally I need 5,000,000 interrupts a second. First question - this is possible...right?

No.

tmmurcko:
Hey everyone,

I'm trying to get interrupts to occur at a very fast frequency using timer1. Ideally I need 5,000,000 interrupts a second.

First question - this is possible...right? Arduino uses 16 MHz clock, so it should be, correct?

Better read this: Gammon Forum : Electronics : Microprocessors : Interrupts

An ISR will have an overhead of around 2.625 µS, so the most number of interrupts you could handle is roughly 381,000 per second, and even that is only if they do nothing useful.

Your concept of handling 5 million interrupts per second is assuming you can enter and leave the interrupt in about 3 clock cycles which ain't gonna happen.

// timer compare interrupt service routine
ISR(TIMER1_COMPA_vect) {       
  digitalWrite(9, digitalRead(9) ^ 1);
}

digitalWrite isn't particularly fast, either. You can toggle pins faster than that, but why not let the timer do it for you?

the most number of interrupts you could handle is roughly 381,000 per second

OK - how come I can't get past the 80KHz frequency then?

You said:

digitalWrite isn't particularly fast, either. You can toggle pins faster than that, but why not let the timer do it for you?

How/what do you mean?

tmmurcko:
How/what do you mean?

Google "Arduino Port Manipulation"

For high speed PWM you need choose prescale=1, and use a fast mode where TOP is set from a register,
such as mode 14 on timer1. Then set TOP (ie ICR1) to a small value - the minimum is 1, the
counter counts from 0 to TOP, so it will wrap every 2 cycles (8MHz).

You can only achieve waveforms where the mark and space segments are a multiple of the
device clock frequency. So 5.333MHz is doable (but at 33% or 66% duty cycle only).

tmmurcko:

the most number of interrupts you could handle is roughly 381,000 per second

OK - how come I can't get past the 80KHz frequency then?

You said:

digitalWrite isn't particularly fast, either. You can toggle pins faster than that, but why not let the timer do it for you?

How/what do you mean?

This is what digitalWrite does, with cycles counts at the end of each line in brackets:

void digitalWrite(uint8_t pin, uint8_t val)
{
        uint8_t timer = digitalPinToTimer(pin);
 180:   48 2f           mov     r20, r24   (1)
 182:   50 e0           ldi     r21, 0x00       ; 0   (1)
 184:   ca 01           movw    r24, r20   (1)
 186:   82 55           subi    r24, 0x52       ; 82   (1)
 188:   9f 4f           sbci    r25, 0xFF       ; 255   (1)
 18a:   fc 01           movw    r30, r24   (1)
 18c:   24 91           lpm     r18, Z+   (3)
        uint8_t bit = digitalPinToBitMask(pin);
 18e:   ca 01           movw    r24, r20   (1)
 190:   86 56           subi    r24, 0x66       ; 102   (1)
 192:   9f 4f           sbci    r25, 0xFF       ; 255   (1)
 194:   fc 01           movw    r30, r24   (1)
 196:   94 91           lpm     r25, Z+   (3)
        uint8_t port = digitalPinToPort(pin);
 198:   4a 57           subi    r20, 0x7A       ; 122   (1)
 19a:   5f 4f           sbci    r21, 0xFF       ; 255   (1)
 19c:   fa 01           movw    r30, r20   (1)
 19e:   34 91           lpm     r19, Z+   (3)
        volatile uint8_t *out;

        if (port == NOT_A_PIN) return;
 1a0:   33 23           and     r19, r19   (1)
 1a2:   09 f4           brne    .+2             ; 0x1a6 <digitalWrite+0x26>   (1/2)
 1a4:   40 c0           rjmp    .+128           ; 0x226 <digitalWrite+0xa6>   (2)

        // If the pin that support PWM output, we need to turn it off
        // before doing a digital write.
        if (timer != NOT_ON_TIMER) turnOffPWM(timer);
 1a6:   22 23           and     r18, r18   (1)
 1a8:   51 f1           breq    .+84            ; 0x1fe <digitalWrite+0x7e>   (1/2)
//
//static inline void turnOffPWM(uint8_t timer) __attribute__ ((always_inline));
//static inline void turnOffPWM(uint8_t timer)
static void turnOffPWM(uint8_t timer)
{
        switch (timer)
 1aa:   23 30           cpi     r18, 0x03       ; 3   (1)
 1ac:   71 f0           breq    .+28            ; 0x1ca <digitalWrite+0x4a>   (1/2)
 1ae:   24 30           cpi     r18, 0x04       ; 4   (1)
 1b0:   28 f4           brcc    .+10            ; 0x1bc <digitalWrite+0x3c>   (1/2)
 1b2:   21 30           cpi     r18, 0x01       ; 1   (1)
 1b4:   a1 f0           breq    .+40            ; 0x1de <digitalWrite+0x5e>   (1/2)
 1b6:   22 30           cpi     r18, 0x02       ; 2   (1)
 1b8:   11 f5           brne    .+68            ; 0x1fe <digitalWrite+0x7e>   (1/2)
 1ba:   14 c0           rjmp    .+40            ; 0x1e4 <digitalWrite+0x64>   (2)
 1bc:   26 30           cpi     r18, 0x06       ; 6   (1)
 1be:   b1 f0           breq    .+44            ; 0x1ec <digitalWrite+0x6c>   (1/2)
 1c0:   27 30           cpi     r18, 0x07       ; 7   (1)
 1c2:   c1 f0           breq    .+48            ; 0x1f4 <digitalWrite+0x74>   (1/2)
 1c4:   24 30           cpi     r18, 0x04       ; 4   (1)
 1c6:   d9 f4           brne    .+54            ; 0x1fe <digitalWrite+0x7e>   (1/2)
 1c8:   04 c0           rjmp    .+8             ; 0x1d2 <digitalWrite+0x52>   (2)
        {
                #if defined(TCCR1A) && defined(COM1A1)
                case TIMER1A:   cbi(TCCR1A, COM1A1);    break;
 1ca:   80 91 80 00     lds     r24, 0x0080   (2)
 1ce:   8f 77           andi    r24, 0x7F       ; 127   (1)
 1d0:   03 c0           rjmp    .+6             ; 0x1d8 <digitalWrite+0x58>   (2)
                #endif
                #if defined(TCCR1A) && defined(COM1B1)
                case TIMER1B:   cbi(TCCR1A, COM1B1);    break;
 1d2:   80 91 80 00     lds     r24, 0x0080   (2)
 1d6:   8f 7d           andi    r24, 0xDF       ; 223   (1)
 1d8:   80 93 80 00     sts     0x0080, r24   (2)
 1dc:   10 c0           rjmp    .+32            ; 0x1fe <digitalWrite+0x7e>   (2)
                #if defined(TCCR2) && defined(COM21)
                case  TIMER2:   cbi(TCCR2, COM21);      break;
                #endif
                
                #if defined(TCCR0A) && defined(COM0A1)
                case  TIMER0A:  cbi(TCCR0A, COM0A1);    break;
 1de:   84 b5           in      r24, 0x24       ; 36   (1)
 1e0:   8f 77           andi    r24, 0x7F       ; 127   (1)
 1e2:   02 c0           rjmp    .+4             ; 0x1e8 <digitalWrite+0x68>   (2)
                #endif
                
                #if defined(TIMER0B) && defined(COM0B1)
                case  TIMER0B:  cbi(TCCR0A, COM0B1);    break;
 1e4:   84 b5           in      r24, 0x24       ; 36   (1)
 1e6:   8f 7d           andi    r24, 0xDF       ; 223   (1)
 1e8:   84 bd           out     0x24, r24       ; 36   (1)
 1ea:   09 c0           rjmp    .+18            ; 0x1fe <digitalWrite+0x7e>   (2)
                #endif
                #if defined(TCCR2A) && defined(COM2A1)
                case  TIMER2A:  cbi(TCCR2A, COM2A1);    break;
 1ec:   80 91 b0 00     lds     r24, 0x00B0   (2)
 1f0:   8f 77           andi    r24, 0x7F       ; 127   (1)
 1f2:   03 c0           rjmp    .+6             ; 0x1fa <digitalWrite+0x7a>   (2)
                #endif
                #if defined(TCCR2A) && defined(COM2B1)
                case  TIMER2B:  cbi(TCCR2A, COM2B1);    break;
 1f4:   80 91 b0 00     lds     r24, 0x00B0   (2)
 1f8:   8f 7d           andi    r24, 0xDF       ; 223   (1)
 1fa:   80 93 b0 00     sts     0x00B0, r24   (2)

        // If the pin that support PWM output, we need to turn it off
        // before doing a digital write.
        if (timer != NOT_ON_TIMER) turnOffPWM(timer);

        out = portOutputRegister(port);
 1fe:   e3 2f           mov     r30, r19   (1)
 200:   f0 e0           ldi     r31, 0x00       ; 0   (1)
 202:   ee 0f           add     r30, r30   (1)
 204:   ff 1f           adc     r31, r31   (1)
 206:   ee 58           subi    r30, 0x8E       ; 142   (1)
 208:   ff 4f           sbci    r31, 0xFF       ; 255   (1)
 20a:   a5 91           lpm     r26, Z+   (3)
 20c:   b4 91           lpm     r27, Z+   (3)

        uint8_t oldSREG = SREG;
 20e:   2f b7           in      r18, 0x3f       ; 63   (1)
        cli();
 210:   f8 94           cli   (1)

        if (val == LOW) {
 212:   66 23           and     r22, r22   (1)
 214:   21 f4           brne    .+8             ; 0x21e <digitalWrite+0x9e>   (1/2)
                *out &= ~bit;
 216:   8c 91           ld      r24, X   (2)
 218:   90 95           com     r25   (1)
 21a:   89 23           and     r24, r25   (1)
 21c:   02 c0           rjmp    .+4             ; 0x222 <digitalWrite+0xa2>   (2)
        } else {
                *out |= bit;
 21e:   8c 91           ld      r24, X   (2)
 220:   89 2b           or      r24, r25   (1)
 222:   8c 93           st      X, r24   (2)
        }

        SREG = oldSREG;
 224:   2f bf           out     0x3f, r18       ; 63   (1)
 226:   08 95           ret   (4)

00000228 <digitalRead>:
}

Of course, all those lines won't be executed, but I think you'll agree that you will probably lose quite a few clock cycles while it does it.

And then there is the digitalRead you are doing.

This goes faster:

// timer compare interrupt service routine
ISR(TIMER1_COMPA_vect) {       
  PINB = bit (1);
}

That line toggles D9, believe it or not. :slight_smile:

But if you set the timer to do the toggling then you don't need an interrupt at all.

Gotcha - makes sense! Thanks for the replies, really appreciate it!