Timer and interrupt not above some 29 kHz

Hello,

I'm trying to get a LED blink at 56 kHz with using an interrupt. At some point the board wouldn't get higher then around 29.5 kHz (measured with a USBee) when changing the compare register. I've used several codes from the net but following code (thanks to http://www.letmakerobots.com) illustrates this the most simplest way:

#define ledPin 12

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

  OCR1A = 142;            // compare match register =16MHz/1/56kHz/2 -1 (troggle) = 142
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS10);    // no prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();             // enable all interrupts
}

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);   // toggle LED pin
}

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

Any ideas how come? :~

I suspect digitalRead()+digitalWrite() are too slow.

Have you thought of using the timer to generate the square wave output directly? There is a pin associated with each Output Compare Register. You can set the timer to toggle the pin each time it hits TOP. No interrupt needed! You can get output frequencies up to 4 MHz for 56 kHz should not be a problem.

The interrupts can't keep up at that rate. You need an interrupt every 8.9375 uS, and it takes around 3 uS to set and and leave an interrupt. Then there is the time taken to read the pin, toggle it, and write it again.

This code generates 56 kHz on pin 9 using the timer but the hardware toggling (no interrupts):

const byte LED = 9;

void setup() {
  pinMode (LED, OUTPUT);
  
  // set up Timer 1
  TCCR1A = _BV (COM1A0);  // toggle OC1A on Compare Match
  TCCR1B = _BV(WGM12) | _BV(CS10);   // CTC, no prescaler
  OCR1A =  142;       // compare A register value (143 * clock speed)
}  // end of setup

void loop() { }

If you do direct port manipulation you can just squeeze it in, in time, using an interrupt:

#define ledPin 12

void setup()
{
  pinMode(ledPin, OUTPUT);
  
  // initialize timer1 
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS10);    // no prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  OCR1A = 142;            // compare match register =16MHz/1/56kHz/2 -1 (troggle) = 142

}

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
  if (PINB & _BV (4))
    PORTB &= ~_BV (4);
  else
    PORTB |= _BV (4);
}

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

digitalRead and digitalWrite are functions (which have an overhead to call) plus they do a table lookup to convert the pin number to the bit number. By doing it yourself you are saving some time.

Ninja'd by johnwasser while I was setting up the tests. :wink:

I'm no AVR expert by any stretch, but in Arduino Internals by Dale Wheat,
he mentions using the following functions to speedup ISRs:

bitSet(PORTB, 5);
bitClear(PORTB, 5);

Doing direct calls to the compiler underlying the Arduino shell, I think. You have
to use the AVR nomenclature as arguments, and not the Arduino nomenclature,
for the pin callouts.

Also works for changing timer settings, etc:

bitSet(TIMSK1, ICIE1);

Yes, you can do that, and it is probably better self-documenting. However the code generated is exactly the same. My code:

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
 100:	1f 92       	push	r1
 102:	0f 92       	push	r0
 104:	0f b6       	in	r0, 0x3f	; 63
 106:	0f 92       	push	r0
 108:	11 24       	eor	r1, r1
{
  if (PINB & _BV (4))
 10a:	1c 9b       	sbis	0x03, 4	; 3
 10c:	02 c0       	rjmp	.+4      	; 0x112 <__vector_11+0x12>
    PORTB &= ~_BV (4);
 10e:	2c 98       	cbi	0x05, 4	; 5
 110:	01 c0       	rjmp	.+2      	; 0x114 <__vector_11+0x14>
  else
    PORTB |= _BV (4);
 112:	2c 9a       	sbi	0x05, 4	; 5
}
 114:	0f 90       	pop	r0
 116:	0f be       	out	0x3f, r0	; 63
 118:	0f 90       	pop	r0
 11a:	1f 90       	pop	r1
 11c:	18 95       	reti

Changing the ISR to read:

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
  
  if (bitRead (PINB, 4))
     bitClear (PORTB, 4);
  else
    bitSet (PORTB, 4);

}

Gives:

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
 100:	1f 92       	push	r1
 102:	0f 92       	push	r0
 104:	0f b6       	in	r0, 0x3f	; 63
 106:	0f 92       	push	r0
 108:	11 24       	eor	r1, r1
{
  
  if (bitRead (PINB, 4))
 10a:	1c 9b       	sbis	0x03, 4	; 3
 10c:	02 c0       	rjmp	.+4      	; 0x112 <__vector_11+0x12>
     bitClear (PORTB, 4);
 10e:	2c 98       	cbi	0x05, 4	; 5
 110:	01 c0       	rjmp	.+2      	; 0x114 <__vector_11+0x14>
  else
    bitSet (PORTB, 4);
 112:	2c 9a       	sbi	0x05, 4	; 5

}
 114:	0f 90       	pop	r0
 116:	0f be       	out	0x3f, r0	; 63
 118:	0f 90       	pop	r0
 11a:	1f 90       	pop	r1
 11c:	18 95       	reti

Every single byte of machine code the same. So it runs at the same speed, to the nanonsecond.

I like it for the better documentation, but don't think it will save time.

Based on one of Nick's proposals I've altered and optimized my code, see below, and it works 100%. This now can go up to 193 kHz. Many thanks to all that have responded!!!

#define ledPin 12

void setup()
{
  pinMode(ledPin, OUTPUT);
  
  // initialize timer1 
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS10);    // no prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  OCR1A = 141;            // compare match register =16MHz/1/56 kHz/2 -1 (troggle) = 142 
                          // in practice use 141 then signal 56.2-56.3 kHz, if 1 used then max is 193.5 kHz


}

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{

  PORTB ^=(1<<6); //pin 12 on Mega 2560

}

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

One little-known feature of the ATmega processor is that each PORT has a PIN register that will do the XOR for you. I expect that will be faster because it doesn't have to do a read/modify/write cycle. Try this:

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
  //  The PIN register, when written, will toggle only those pins whose bits are set to 1.
  PINB = (1<<6); // Toggle pin 12 on Mega 2560.  Equivalent to bitSet(PINB, 6);
}