The overhead of Arduino Interrupts

I wanted to build an application that could sniff  i2C transactions.  In particular, I was interested in the actual data transactions between the Wii NunChuk and WiiRemote.  This particular data interchange utilizes 400KHz i2C protocol.  So, I thought this was a good opportunity to use arduino interrupts, especially the change on pin interrupts.  What I envisioned is a state-machine that would perform work on transitions of either SCL or SDA – or so I thought.

What I found out was the overhead in the simple version of attaching interrupts was too much to do anything useful at the 400KHz data rate.  I changed to edge-sensitive interrupts without any improvements.  I had to find out what was going on.

I started looking at the actual assembly code generated from the “attachInterrupt” arduino reference library.  Here’s what I found.

Here’s a simple code example:

#define LED_PIN 5  // digital pin #13 (portb)
#define LED_ON() PORTB |= _BV(LED_PIN)
#define LED_OFF() PORTB &= ~_BV(LED_PIN)

void myfunc() {
  LED_OFF();
}
void setup() {
   pinMode(13, OUTPUT);
  attachInterrupt(0, myfunc, RISING);
}

void loop() {
  LED_ON();
  delayMicroseconds(20);
}

I tied the LED output back to Digital pin #2 and this is running on a 16MHz Arduino.  Notice, all this program does is turn an LED on then the interrupt, which responds to the rising of the LED voltage, turns off the LED.  Here is a picture of the LED “on” strobe:

img_25261

As can be seen, the width of the LED “on” is 3.435usec, which is considerable longer than the 600ns that SCL of the i2C protocol is high!  So, this won’t work to respond to SCL changes.  This is about 55 clock cycles @ 16MHz!  What is going on?  Also, what about the 20usec delay in the loop?

img_2527

Notice that the total period for the LED is 26.60usec.  That means the off time for the LED is 23.165usec, which is 3.165usec longer than the delay statement request.   Let’s now look at what happens under the hood.  To do that we have to look at the assembled instructions that came from the program’s compile.

The following is what is generated for the interrupt handler for INT0.  Using the atmegas168 datasheet and information about instructions we can count the number of instruction cycles.  There are 45 cycles before there is a call to the “myfunc” and there are 35 cycles after return of “myfunc” and return from the interrupt handler.  There is also 3 cycles in the pin synchronizer and another 3 cycles for a JMP instruction (which is in the interrupt vector table).  So, we have a total of 51 cycles before we start to execute “myfunc”.

capture4

Now the “myfunc” has some amount of delay before it sets the LED off.   As can be seen, the overhead for a “cbi” instruction  is about 1 cycle.  We’ve accounted for 52 cycles, there are another in 2 cycles in the “sbi”  instruction in the “loop” that actually turns the LED on, and also this blocks interrupts until after the instruction completes.  Now we’ve accounted for 54 cycles.  The other cycle is likely due to the fact that we are setting the LED synchronously with the same clock as the interrupt sampling; this will add another cycle for synchronization.  There we are, all 55 cycles accounted for!

capture51

So, the simple to use “attachInterrupt” has a too much of a significant overhead for the application that I would like to do.  I’ll have to find another solution. 

I made changes to the program.  I got rid of the generic “attachInterrupt” and manually enabled the interrupt.  I also used the “ISR” construct to declare the interrupt handler.  To compile this I had to unfortunately modify Arduino’s “WInterrupts.c” source (located in the \hardware\cores\arduino folder) – I commented out the SIGNAL operations for INT0_vect as this conflicts with the use of “ISR” statement in my application.

#define LED_PIN 5  // digital pin #13 (portb)
#define LED_ON() PORTB |= _BV(LED_PIN)
#define LED_OFF() PORTB &= ~_BV(LED_PIN)

ISR(INT0_vect) {
  LED_OFF();
}
void setup() {
  pinMode(13, OUTPUT);
  // enable INT0 interrupt on change
  EICRA = 0×03;  // INT0 – rising edge on SCL
  EIMSK = 0×01;  // enable only int0
}

void loop() {
  LED_ON();
  delayMicroseconds(20);
}

The changes to the actual waveform on LED were remarkable:

img_2528

The overhead of 1.118usec is now only about 18 clock cycles.  Unfortunately, this is still too much for me to use interrupts to process i2C message.  In addition, it may now be obvious that the background interrupts to keep the delay clock alive are also adding cycles everytime that supporting interrupt occurs.  This extra delay occuring at random places in your application could be significant and cause the occasional strange behavior.

This is a good example of using the instruction and cycle counting methodology to better understand how to improve critical timing in an application.

cheers!

About these ads

Tags: , , ,

5 Responses to “The overhead of Arduino Interrupts”

  1. bob Says:

    will this run ok on a atmega328

    • billgrundmann Says:

      I don’t see why it would not run, but I can’t assure it since I don’t have one. Please let me know if you have any particular problems with porting to the atmega328.

      My postings are meant to give people some additional insight and how to possibly take advantage of the underlying technology and are not meant to provide canned solutions.

  2. Richard Says:

    Bill,

    I stumbled across this entry while searching for information on the overhead associated with various Arduino functions. Needless to say, I found this entry (as well as much of the rest of your blog) to be very useful. You’ve done an excellent job with thoroughly explaining and illustrating your point.

    Keep up the good work.

  3. Michael Dreher Says:

    Thanks for the comprehensive information about IRQ overhead.

  4. Russ Says:

    I too have been struggling with interrupts. My UNO-R3 reports a latency of 5 us from rising input on INT0 to getting into the ISR where I toggle a pin. I use the ISR (INTx_vect) construct and it compiles fine with no core mods. But way too long! Your insights are very constructive and I appreciate them.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: