Go Down

Topic: Timer and interrupt not above some 29 kHz (Read 909 times) previous topic - next topic

Eagle67

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:

Code: [Select]
#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? :~

johnwasser

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.
Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

Nick Gammon

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):

Code: [Select]
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:

Code: [Select]
#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.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Nick Gammon

Ninja'd by johnwasser while I was setting up the tests. ;)
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

oric_dan

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);


Nick Gammon

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

Code: [Select]
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:

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

}


Gives:

Code: [Select]
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.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Eagle67

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!!!

Code: [Select]

#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...
}

johnwasser

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:

Code: [Select]

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);
}

Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

Go Up