8

I have an isr that is updating a display at a fixed frequency. I would like to tune my routine to minimize overhead and keep as much cpu time as possible open for other processing, but I don't have any good way to collect metrics to determine my cpu load.

I could look at the assembly and analyze the routine, but I don't have the patience or the ability to do that accurately. I don't feel like I need terribly fine grained results either, just a simple percentage of cpu time occupied by the isr.

I could set a pin high only when the isr is active and measure it externally. That has a minimum of overhead in the code, but I do not know what to measure it with. I don't have an oscilloscope or anything like that. Is there a simple ic or an easy way to use another micro to measure duty cycle? I have heard of dedicated frequency counter chips, but is there anything for duty cycle?

Jon L
  • 4,258
  • 1
  • 22
  • 33
captncraig
  • 2,054
  • 3
  • 24
  • 45

4 Answers4

11

Set an output pin when you enter the ISR and clear it before returning from it. Filter the output with an RC filter. The voltage across the capacitor should give you the ISR duty cycle.
For instance, if your power supply is 3.3V and you measure 33mV, then you spend 1% of the time in the ISR.

stevenvh
  • 145,145
  • 21
  • 455
  • 667
  • 2
    This is exactly what we do, except for the RC filter part. We set an output pin on entering the ISR, then clearing it on return. Looking at this on an o-scope will give all sorts of useful info. –  Nov 07 '11 at 18:03
  • 3
    @David - I also just watch the pulses on a scope, but OP says he doesn't have one. – stevenvh Nov 07 '11 at 18:05
  • 1
    It looks like I need to start saving for a good scope. – captncraig Nov 07 '11 at 19:40
  • @stevenvh, exactly what I was going to suggest when I saw the absence of a scope. This is why those embedded guys love a few extra digital out pins. – Kortuk Nov 07 '11 at 20:03
  • Filtering to meter in absence of a scope a nice idea. Turn LED on/off and measure effective brightness :-).(Very hard to see even quite large variations - but cute.) – Russell McMahon Nov 07 '11 at 22:14
  • Wouldn't the voltage across the capacitor be dependent on the discharge resistor as well? – Simon Richter Nov 08 '11 at 07:28
  • @Simon - It has to, otherwise it would give a peak value, being Vdd. The charging and discharging results in the average voltage. – stevenvh Nov 08 '11 at 08:19
  • @stevenvh, how would I choose an appropriate value? – Simon Richter Nov 08 '11 at 08:29
  • @Simon - not critical, though a high RC value will stabilize slower and react slower to changes. If you have 1k interrupts per second (1ms period) I would choose RC = 20ms – stevenvh Nov 08 '11 at 09:01
8

Just a half-baked idea, you might be able to make use of timers like so (pseudo code):

int main(void)
{

    /* ... init timers and uart here, enable your interrupts ... */

    start_timer0();
    while (!timer1Started()){}
    stop_timer1();

    uart_puts("Idle ticks: %d, ISR ticks: %d", timer0_value, timer1_value);

}

and in your display ISR...

ISR_display()
{
    stop_timer0();
    start_timer1();

    /* ... your ISR routine ... */
}

I've made a few assumption here. 1 - that you're not using your timers for anything else, and that, 2 - the overhead of starting and stopping a timer is minimal (typically done with a single register write). EDIT: and a 3rd assumption, you can capture all of this before a timer overflow occurs, but maybe you can account for that as well.

There will be some context switch overhead that you won't be able to catch, and this also adds two additional operations in your ISR (be sure to use macros for your start_timer/stop_timer to eliminate function call overhead). If you can get the total number of cycles used for start+stop timer macros, then you can subtract those ticks from the timer1_value to get the ISR ticks value a little more accurately. Your final calculation for % of CPU time used would simply be:

$$ Usage_{cpu} = (\frac{Ticks_{isr}}{Ticks_{isr} + Ticks_{idle}}) * 100 $$

Jon L
  • 4,258
  • 1
  • 22
  • 33
  • 1
    +1 for a solution requiring no external hardware. Probably a more cumbersome way to do it, but it fits in my constraints. I just need to learn about timers now. Thanks. – captncraig Nov 07 '11 at 19:44
  • I used this on an AVR for a similar project. It works very well. – drxzcl Nov 07 '11 at 22:26
7

The easiest way to do this is to run the code in the simulator and measure the cycles taken by the interrupt routine. The Microchip MPLAB simulator, for example, has a handy stopwatch feature that is very useful for this purpose.

Failing that, raising a pin at the start of the interrupt and lowering it at the end can help. The easiest way to look at that is with a oscilloscope. If you are doing microcontroller and electronics projects, you should get one anyway.

Without a scope, you could simply low pass filter the pin voltage, then measure it with a voltmeter. That voltage divided by the processor power voltage will give you the fraction of the time the pin is high. For example, if the processor is running on 3.3V and the low pass filtered pin voltage is 800mV, then the pin is high 800mV / 3.3V = 24% of the time.

Since you are apparently using a compiler, you need to derate this answer somewhat. The compiler is likely adding some interrupt entry code before your code runs and interrupt exit code after your code runs. The true interrupt time will extend a few cycles on either side of the pulse. Of course if you care about interrupt timing, you shouldn't be using a compiler in the first place.

Olin Lathrop
  • 310,974
  • 36
  • 428
  • 915
0

I found an interesting way to measure interrupt load on Arduino. It seems that ISRs slow down the delayMicroseconds() function and make it inaccurate, which can be used to detect how many cycles are used in ISRs. The full code is here (with a bug that delayMicroseconds seems to be limited to 1000 when ISRs are disabled): https://github.com/Palatis/arduino-softpwm/blob/9567314ef35e31d8483e6d0c5dda6f33e67d4be0/src/SoftPWM.h#L214

unsigned long time1, time2;

interrupts(); // enable interrupt
time1 = micros();
delayMicroseconds(1000);
time1 = micros() - time1;

noInterrupts(); // disable interrupt
time2 = micros();
delayMicroseconds(1000);  // MAX while interrupts are disabled is 1000
time2 = micros() - time2;
interrupts(); // enable interrupt

float cpuFrequency = F_CPU;
float load = float(time1 - time2) / time1;
float cycles_per_interrupt = load * cpuFrequency / interrupt_frequency;

For other MCUs one would need to analyze how the delayMicroseconds() function works internally. It should also be possible to hardcode time2 to 1000 in this example.

cat
  • 131
  • 4