DUE interrupt latency

Bill Grundmann's Blog (The overhead of Arduino Interrupts | Bill Grundmann's Blog) has a nice discussion of interrupt overhead on AVR 168. I ran a simple DUE sketch with pin 13 tied to pin 2 to measure number of cycles (systick) to enter and exit an interrupt ISR using standard attachInterrupt() ISR and a version where I modified the core ISR handler. I ran similar sketches on maple and teensy3. The results are in isrperf.txt and the DUE sketch is isrperf.ino at the following github site

So the standard latency is 355+128 and modified 53+28.

At what rate dies the systick counter tick so we can calc the actual time?


Rob

You sir are a gentleman and a scholar. I really like your git repo and the work you're doing. The core library for Due needs a lot of work to make it what it should be and your work is a big help in that.

Why thanks Adder, that's very kind of y...Oh, you mean mantoui :slight_smile:


Rob

When measuring interrupt latency for the DUE one has to be a bit careful. If you attach an interrupt to a pin using attachInterrupt(), the latency depends on the pin that you choose. The reason is the following:

The ARM detects interrupts only for a whole PORT and sends a 32bit word with the information on which pin/pins on this PORT an interrupt was triggered. The code that handles this word and decides which ISR should be called, is in the "WInterrupts.c" file (shown for port A only):

void PIOA_Handler(void) {
	uint32_t isr = PIOA->PIO_ISR;
	uint32_t i;
	for (i=0; i<32; i++, isr>>=1) {
		if ((isr & 0x1) == 0)
			continue;
		if (callbacksPioA[i])
			callbacksPioA[i]();
	}
}

As you can see, it scans through each bit of the 32bit word "isr" that corresponds to the 32 pins on port A and checks if an interrupt was triggered on this pin or not (pin corresponding bit either 1 or 0). Then it calls the interrupt service routine that was previously stored for this pin in "callbacksPioA".

The problem with this code is, that running through the loop without calling any service routines already takes about 300 clock cycles (by measuring I find something like 4µs). This means that the first pin on each port has a latency on the order of 120ns and the last one of about 4µs. What I don't like about this code is also the fact that it scans through all the 32 pins (meaning 4µs dead time for the processor) even if you have only one interrupt attached.

I think this problem can be quite easily circumvent by storing the bitmasks for the pins that you attached interrupts to and just let the sum running over the number of interrupts that were attached so far. Then using the stored bitmasks it would be quite easy to see where an interrupt was triggered by looking only at the pins for which an interrupt was already defined.
This would massively reduce both the average interrupt latency as well as the caused processor dead time for most applications where you only use a couple of interrupts. (Of course if you use interrupts for all 32pins on a port, then you have to live with these delays and deadtimes)

I might do some coding later on and see if I can implement something like this.

Just as a comment to your data:
This is actually why you see 355ticks "in to ISR" and 128 "out of ISR" for your pin 2 (which is pin 25 on PORT B):
The ratio of "in to ISR" to the total time 355/(355+128) = 0.73 is almost the same as your "pin on port" number to total pin numbers 25/32 = 0.78

How can I define my own PIOA_Handler function without modifying the IDE code. I see that the function pointer is placed in the exception table (which is a const) which goes to .vectors section. How do I modify this table and replace the PIOA_Handler with myPIOA_Handler.

tried this code -

static void PIOA_attach_handler()
{
	Serial.println("attaching interrupt handler");
	DeviceVectors * ptr = (DeviceVectors *) &(exception_table);

	Serial.print("pioa handler address original - ");
	Serial.println((unsigned int )exception_table.pfnPIOA_Handler, HEX);
	Serial.print("new pioa handler address - ");
	Serial.println((unsigned int )&myPIOA_Handler, HEX);

	ptr->pfnPIOA_Handler = (void *) &myPIOA_Handler;
	Serial.print("pioa handler address after change, source - ");
	Serial.println((unsigned int )ptr->pfnPIOA_Handler, HEX);

	Serial.print("pioa handler address after change - ");
	Serial.println((unsigned int )exception_table.pfnPIOA_Handler, HEX);
}

i got this output, new handler doesnt goto the table.

attaching interrupt handler
pioa handler address original - 845E9
new pioa handler address - 801DD
pioa handler address after change, source - 845E9
pioa handler address after change - 845E9

I tried to modify the sources (startup_sam3xa.c) in the IDE, hoping I could get it to work that way. But the exception table is unfortunately buried inside an already built lib - hardware/arduino/sam//variants/arduino_due_x/libsam_sam3x8e_gcc_rel.a(startup_sam3xa.o). So just changing the sources wont help, need to compile those too. Anyone tried building this library, makefile included in that area didnt work out of the box.

The actual interrupt handler is defined in WInterrupts.c as:

void PIOA_Handler(void) {
	uint32_t isr = PIOA->PIO_ISR;
	uint32_t i;
	for (i=0; i<32; i++, isr>>=1) {
		if ((isr & 0x1) == 0)
			continue;
		if (callbacksPioA[i])
			callbacksPioA[i]();
	}
}

What you want to do is override this function. You can do that but you have to define it as weak. Add this line before that function:
void PIOA_Handler(void) attribute((weak));

Now, you are free to create a function that matches the prototype in your sketch and it will actually override the Arduino core version properly at link time. This requires changing the arduino core source just a bit so it doesn't quite fit the bill for what you originally wanted but it is close.

thanks a lot.. that worked fine... i can live with this for the time being:)

Hi.

Even if the tobic is old maybe some people ar still reading it..
So if I understand everything this should work:

void PIOA_Handler(void) attribute((weak));

void PIOA_Handler(void) { //Interrupt on pin 42 (A.19)
uint32_t isr = PIOA->PIO_ISR;
if (isr==0B10000000000000000000){ //19
// do something
}
}

The next question is: If I override the PIOA_Handler is for example the serial1 function affected ?
And by the way: which Timer Interrupts are used by Arduino for delay and so on ?
Or in general is there a list with all Interrupts used by Arduino ?

Serial uses the serial hardware, not the PIO.