Pages: [1]   Go Down
Author Topic: Timer and interrupt not above some 29 kHz  (Read 846 times)
0 Members and 1 Guest are viewing this topic.
Netherlands
Offline Offline
Newbie
*
Karma: 0
Posts: 3
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
#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? smiley-confuse
Logged

Massachusetts, USA
Offline Offline
Tesla Member
***
Karma: 201
Posts: 8689
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

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

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 474
Posts: 18696
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
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:
#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.
Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 474
Posts: 18696
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Ninja'd by johnwasser while I was setting up the tests. smiley-wink
Logged

the land of sun+snow
Offline Offline
Faraday Member
**
Karma: 158
Posts: 2887
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 474
Posts: 18696
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

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:

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

}

Gives:

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 (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.
Logged

Netherlands
Offline Offline
Newbie
*
Karma: 0
Posts: 3
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Massachusetts, USA
Offline Offline
Tesla Member
***
Karma: 201
Posts: 8689
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

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

Pages: [1]   Go Up
Jump to: