3
\$\begingroup\$

I'm using an external interrupt to measure the frequency of a signal, the AVR clock is at 8MHz. I'm essentially counting the ticks between pin toggles using a 16 bit timer (with some handling for overflows for slow signals).

The skeleton code is as follows:

int main(void){
clearbit(DDRD,PD2);
PCMSK3 = 0x00;
setbit(PCMSK3, PCINT26);
setbit(PCICR,PCIE3);
setbit(TIMSK1,TOIE1);
setbit(TCCR1B, CS10);

while(1){
}
return 0;
}

ISR(PCINT3_vect){
    togglebit(PORTD, PD1);  
    countl = TCNT1L;
    counth = TCNT1H;
    soverflow = overflow;
    overflow = 0;
    TCNT1 = 0x0000;
    return;
}

/* Overflow timer */
ISR(TIMER1_OVF_vect){
    overflow++;
    return;
}

This works pretty much as desired, however I'm unable to measure anything shorter than around 49 cycles. The two scope images show the problem. The yellow trace is from the external oscillator, the blue trace is the pin toggle inside the interrupt handler. There is nothing in the main loop, and having things there doesn't change the results significantly. All the variables are uint8_t. Reading the count in high and low bytes gained a bit of speed (compared to a single 16 bit read/write).

The best I've been able to do is ~95kHz, but I'm curious as to where the lost cycles are going. The assignments can't be more than a few cycles each and if I understand right, ISR basically calls cli() on entry and sei() on exit, so the only thing that happens is the timer gets reset, the function returns and we wait for the next toggle.

Scope trace for low frequency

enter image description here

EDIT

void get_count(void){
    uint8_t i=0, oldpin, newpin;
    setbit(TIMSK1,TOIE1);
    setbit(TCCR1B, CS10);
    sei();
    while(getbit(PIND, PD2) != 0){
    continue;
    }
    while(getbit(PIND, PD2) != 1){
    continue;
    }
    TCNT1 = 0;
    while(getbit(PIND, PD2) == 1){
        continue;
    }
    countl = TCNT1L;
counth = TCNT1H;
    TCNT1 = 0;
    soverflow = overflow;
    overflow = 0;
    clearbit(TIMSK1,TOIE1);
clearbit(TCCR1B, CS10);
    cli();
    return;
    }

This gets me down to around around 400kHz before things start getting iffy. Not sure there's much more I can do to make it lower. I wait for the pin to transition to 1, start the timer and time how long it takes go to zero, i.e. half a clock cycle.

enter image description here enter image description here

\$\endgroup\$
4
  • 4
    \$\begingroup\$ No, there is a lot more going on in ISRs, you can see this by disasembling your code. Take a look here help? \$\endgroup\$ Apr 7, 2013 at 6:08
  • \$\begingroup\$ Thanks, interesting read! I'll try writing a tight loop instead, see how that pans out. If possible I'll put scope results up for both so people can see the difference. \$\endgroup\$
    – Josh
    Apr 7, 2013 at 6:27
  • \$\begingroup\$ I think I'm going to call it a night, apologies for the ninja editing! I'm keeping interrupts on because I need to be able to deal with large counts too - for low light levels. Shame the sensor isn't really much use outdoors, sunlight saturates it like crazy, I really want it for the other end of the spectrum - very low frequency work. I was just curious to see what sort of acquisition limit I could get. \$\endgroup\$
    – Josh
    Apr 7, 2013 at 7:33
  • \$\begingroup\$ @angelatlarge, if you want the credit, copy that to another answer and I'll mark it as solved. \$\endgroup\$
    – Josh
    Apr 7, 2013 at 13:13

1 Answer 1

3
\$\begingroup\$

As @angelatlarge said your first task is to view the disassembly of your interrupt vector to determine how many cycles your code actually takes. Second, you have to realize that there is additional overhead associated with a call to an interrupt vector. Here is an extract from the ATMega328P datasheet, section 7.7.1 - this should be representative of all AVRs:

The interrupt execution response for all the enabled AVR interrupts is four clock cycles minimum. After four clock cycles the program vector address for the actual interrupt handling routine is executed. During this four clock cycle period, the Program Counter is pushed onto the Stack. The vector is normally a jump to the interrupt routine, and this jump takes three clock cycles. If aninterrupt occurs during execution of a multi-cycle instruction,this instruction is completed before the interrupt is served. If an interrupt occurs when the MCU is in sleep mode, the interrupt exe-cution response time is increased by four clock cycles. This increase comes in addition to the start-up time from the selected sleep mode. A return from an interrupt handling routine takes four clock cycles. During these four clock cycles, the Program Counter (two bytes) is popped back from the Stack, the Stack Pointer is incremented by two, and the I-bit in SREG is set.

So, to sum it up:

  1. 4 clock cycles to push PC onto the stack
  2. 3 clock cycles to jump to interrupt handler
  3. Up to 3 clock cycles if a multi-cycle instruction is interrupted (AVR instruction set table in the same document indicates that 4 cycles is the longest instruction)
  4. 4 more clock cycles when exiting the interrupt to restore PC, increment SP, etc.

This adds anywhere from 11-14 additional cycles of overhead on top of however many cycles your interrupt code takes.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.