Need some help with nested interrupts

aka_ptt

Member
Hello,

I have an application running on a teensy 4.1 with a SPI based ADC running at a very high sample rate. There is one interrupt per sample and each interrupt must do a SPI read. I also have a 10 second interrupt based timer that sets a flag on each timer interrupt. Loop checks that flag and dumps some statistics every ten seconds. I'm omitting code here because this all works.

Now we get to the problem I'm trying to solve. I would like to run my sample clock rate "at the limit" which means sometimes the hardware sample clock (falling edge of a digital pin) happens before the ISR has returned. I would like to skip the work portion of my if that occurs but increment a counter each time I do it.

My "vanilla" ISR is as more or less as follows:

void myISR()
{
int32_t data = SPI.transfer32();
myWorkFunction(data); // some filtering and transfer of data to a queue to be read by "loop"
}

In this case, as one would expect, if I cross the "at the limit" threshold for the interrupt rate, eventually the "loop()" function never gets called as I am perpetually servicing the interrupt.

What I would like to do is the following:

volatile bool isAdcBusy = false;
volatile uint32_t busyCount = 0;

void myISR()
{
if (isAdcBusy)
{
busyCount++;
return;
}
isAdcBusy = true;
sei(); // See explanation below
int32_t data = SPI.transfer32();
myWorkFunction(data);
isAdcBusy = false;
}

The documentation for sei() seemed to indicate that calling it in the ISR would reenable interrupts allowing the next sample clock interrupt to call "myISR()" even if "myWorkFunction()" had not yet returned from the previous ISR. In that case busyCount would be incremented but the work portion would be skipped.

If this worked, my expectation would be that when I up the clock just past the "at the limit" threshold, "loop()" would still be called and "busyCount" would be incremented roughly every other sample. Instead, I find that the two forms of the ISR exhibit identical behavior: as I up the clock rate I go from loop being called with a busyCount of 0 to loop never being called at all.

This tells me that I misunderstood the documentation for "sei()" and maybe it will not allow the nesting I am seeking to accomplish. If sei() doesn't do what I want, I would like to know if there is any function available to reenable the sample clock interrupt that is calling myISR() at the point where the above code fragment has a call to sei().

Please note that the discussion of upping the clock rate till it is past the "at the limit" threshold is simply how I intended to test the second form of the ISR. In real life my code will be operating where "myWorkFunction" usually finishes on time but occasionally it will take long enough to "miss" an interrupt. I need to keep stats on how often that happens.

Thanking you in advance
 
Last edited:
One way to debug interrupts is use a digital pin (with fast writes or low-level register manipulation) so you can monitor the entry/exit using a 'scope.
 
And thinking some more it may be that most of the time in your interrupts is register dumping and restoring - can you make the ISR just write the value to the buffer and do nothing else, without calling subfunctions?

Maybe look into DMA control of the transfers too - more complicated but way less processor load and guaranteed timing (with luck).
 
The sei() function is meant for AVR, where a single bit manages whether interrupts can happen.

ARM is much more sophisticated. Priority levels are the main mechanism which manages whether an interrupt can run. There is a single bit which can disable all interrupts. But unlike AVR where the global disable is set when any interrupt runs, on ARM the global disable isn't disabling anything. It doesn't change when entering and leaving interrupts. Instead the priority levels do what that 1 bit would have done on AVR, but with 16 levels of priority. So sei() to turn on interrupts doesn't do anything on ARM, because they're already turned on (but only higher priority ones can interrupt your current interrupt code).

ARM and the IMXRT peripherals also give you much more access to interrupt status than AVR. To accomplish this goal (or at least my understanding of it) you might just read the interrupt pending status from the GPIO registers and/or the ARM NVIC. This can let you discover whether another interrupt is pending. You can cancel a pending interrupt (of course at the same or lower priority than your interrupt function, otherwise it would have already interrupts and run) by manipulating the interrupt pending bits within the GPIO and NVIC.
 
Hi Paul,

If I read your comment correctly, you are recommending that I test the interrupt pending bit for the GPIO pin causing the interrupt (or the ARM NVIC) and cancel it if necessary. That makes a lot of sense to me and more like what I was looking for: the sei call was the only thing I could find in the documentation and search of this forum. However I knew there are multiple interrupt levels on the ARM and a single call to reenable did seem a bit simplistic.

Since the interrupt in question is always the next edge transition on the conversion done signal, I think the GPIO version will probably be the more "focused" solution. Assuming I get it to work, I'll post a reply that summarizes the specifics in case anyone else is interested in this topic.

Thank you for your help and also for the Teensy 4.1: GREAT product!

Paul T
 
Sorry to trouble you, but I am having a bit of trouble finding the .h files to include in order to access the interrupt pending bits so I can read and/or clear the one in question (Teensy 4.1 digital pin 15). If you or anyone could point me at it, I would appreciate it.

EDIT: I think I found it in imxrt.h Line 5624 defines the structure. The address for GPIO 6, which I think contains GPIO Pin 15 as bit 19, is 0x42000000. I think I just need to test pin 19 in the PIO6.ISR at the end of my interrupt service routine and, if it is set, then increment my overflow counter and clear the interrupt by writing a 1 to that bit.
 
Last edited:
First clear it at the source in the GPIO register. Then you probably also want NVIC_CLEAR_PENDING(IRQ_GPIO6789).
 
Just a final update. The solution of clearing GPIO6 ISR bit 19 (corresponding to Teensy pin 15) followed by the NVIC_CLEAR_PENDING(IRQ_GPIO6789) did the trick. I was able count missed conversion cycles. I even threw in a bit test of the ADC conversion busy signal to catch the case of the SPI readout activity continuing into the start of the next ADC conversion (a potential noise source).

I'd throw in a code sample except that all this work is taking place in a lab with no internet and to way to get data out.

Thank you for your help.
 
Last edited:
Back
Top