Allocation of software triggered interrupts

PaulStoffregen

Well-known member
Some mechanism is needed for different software projects to allocate software triggered ARM interrupts.

Currently, I am aware of 3 projects with this need: Teensy Audio Library, Frank's MP3 Playing, and Andrew's new work on USB Host Shield.

Perhaps someday the core library should have an API for allocating unused interrupt vectors, similar to DMAChannel.h. In the short term, we'll probably just coordinate our efforts by discussion on this thread.


Known In-Use IRQ Priorities:


0: Systick
64: Serial1, Serial2, Serial3
112: USB
128: Default -- all interrupts that don't use NVIC_SET_PRIORITY()
255: Audio Library update


Fixed Software Interrupt Allocation:
(until we have a dynamic system)

Code:
        Teensy 3.0    Teensy 3.1
        ----------    ----------
Audio       45            94
MP3                       56
UHS          5            17
 
Last edited:
Would'nt it be possible to check the vectorstable ? If there's a vector != default, it is in use and we have to choose another one.
But of course, we could think of a global "interruptallocator" - which simply does the above. Then, the use of it would be optional.
 
Paul and other experts,
could you elaborate a little bit on the purpose of SW interrupts?
I, myself, tend to associate SW interrupts with parallel processing, i.e. multithreading, which we are not (yet?) doing on Teensy. Do I miss some programming paradigm or CPU /DMA features, that ask for software interrupts? Some more specific pointers into the three examples you are mentioning would be appreciated, maybe with some reasoning why its done this way.

thanks
Walter
 
WMXZ, for the MP3-playing it is indeed a kind of multithreading. It has the simple reason, that the audio library need every 2.9 ms new data, but processing of one single mp3-frame needs much more time because of its complexity. It is therefore done in the background in a additional SW interrupt, which is called by the audio-lib. This way the things can be kept simple and easy to use.
 
Something like this ?

Code:
const unsigned char swInterrupts[] = {/*33,*/39,44,53,54,55,56,57,58,59,67,68,69,70,71,72,91,92,93,94,95,96,98,102,108,109,0};

unsigned char allocSwInterrupt( void (*userSwInterrupt)(void) ){
	unsigned char n = 0;
	while (swInterrupts[n] > 0) {
		int interrupt = swInterrupts[n];
		if ((int) _VectorsRam[interrupt] == (int) _VectorsRam[33]) {
			_VectorsRam[interrupt] = userSwInterrupt;
			return interrupt-16;
		}
		n++;
	}
	return 0;		
}

void freeSwInterrupt( int n ){
	_VectorsRam[n] =_VectorsRam[33];
}

Of course, instead of "_VectorsRam[33]" the adress of "unused_vector"

oops...i think i have a little bug in my mp3lib.. :) 16 too much... i'll update it :)
Edit: lol..it's too late here...or too much beer ? :) ..i'm now confused about the offset of 16... maybe you have to correct the example above
 
Last edited:
Would'nt it be possible to check the vectorstable ? If there's a vector != default, it is in use and we have to choose another one.

Yes, but hopefully it will be done late enough that any other fixed use of attachInterruptVector() will already have it allocated.

There's also a small matter of actually testing if those unassigned slots really work. I'm pretty sure they will, but I want to build some test code and actually verify on real hardware.


Are there any details about the Freescale M7 ?

So far, no detailed info.

We have a NDA with Freescale, so if I did have that info, I wouldn't be able to share it until Freescale publishes it on their website.


Paul and other experts,
could you elaborate a little bit on the purpose of SW interrupts?

Usually they're used to do some compute intensive or other slow work which is timing sensitive. A relatively low interrupt priority is assigned, so it can be interrupted by all the other more urgent interrupts, but it still takes priority over the main program.


I, myself, tend to associate SW interrupts with parallel processing, i.e. multithreading, which we are not (yet?) doing on Teensy.

Yes, we pretty much are doing that now.


Some more specific pointers into the three examples you are mentioning would be appreciated, maybe with some reasoning why its done this way.

In the audio library, one of the the DMA interrupts has "update responsibility", meaning it must call AudioStream::update_all().

https://github.com/PaulStoffregen/Audio/blob/master/output_dac.cpp#L152

That function is just a wrapper for triggering a low priority software interrupt. That low priority interrupt actually calls the update() function on every audio object, which causes them to receive, modify, generate and transmit blocks of audio within the library, so the next high priority DMA interrupt can move the data on/off chip.

I haven't had time to look at Frank's work with the MP3 code yet.... and I *really* want to and hope to do so soon. My understanding is he's doing something similar, to schedule the MP3 decoding, which processes 26 ms blocks of audio (1152 samples), which then feeds into the audio library, which processes smaller 2.9 ms blocks (128 samples).

Andrew hasn't published code yet. My understanding from what he's said so far is it's responding to USB host hardware events with a high priority interrupt, then scheduling a variety of work to be done regarding whatever USB devices its managing.


Something like this ?
(snip: code)

Yes, probably something like that.

Perhaps it'll be wrapped in a C++ object API, like DMAChannel.h? I honestly don't have all the answers, really more questions and contemplation at this early stage.

My hope for now is to at least coordinate our efforts to divvy up the available IRQ numbers, as a short-term solution.
 
Perhaps these could be acquired on an as-needed basis. If there are three to go around, why not have a call that sets the next thing to do, and does the sw interrupt. Even if only one works, when the isr is hit, it would have a list of things to run.
 
This is how I would do SWI slot allocation... or rather, NOT do it.

Code:
void (*swis)(void)[3];

int set_SWI(void(*funct)(void)) {
        int rv = 0;
        noInterrupts();
        if(!swis[0]) {
                swis[0]=funct;
                trigger_swi(0);
        } else if(!swis[1]) {
                swis[1]=funct;
                trigger_swi(1);
        } else if(!swis[2]) {
                swis[2]=funct;
                trigger_swi(2);
        } else {
                // Fail, no slots left!
                rv = -1;
        }
        interrupts();
        return rv;
}

// ISR Handlers:

void SWI_0(void) {
        noInterrupts();
        void (*swi)(void) = swis[0];
        swis[0] = NULL;
        interrupts();
        swi();
}

void SWI_1(void) {
        noInterrupts();
        void (*swi)(void) = swis[1];
        swis[1] = NULL;
        interrupts();
        swi();
}

void SWI_2(void) {
        noInterrupts();
        void (*swi)(void) = swis[2];
        swis[2] = NULL;
        interrupts();
        swi();
}

  1. SWI is dynamically allocated.
  2. No particular library is able to 'own' any particular SWI.
  3. Because SWI is typically not cycle time-critical, it isn't an issue that the little bit of extra work is done.
 
This is how I would do SWI slot allocation... or rather, NOT do it.

so, why NOT? (I'm only curious)

Would it be possible to pass two pointers to the callback functions, one for context and one for data?
 
As I'm interested to play with this, could someone construct a simple main(), where two long 'tasks' interrupt each other with swi?
This may be a standard textbook example, but I'm struggling to do it on a single CPU without OS and without timers, which their own ISRs.
 
  1. Because SWI is typically not cycle time-critical, it isn't an issue that the little bit of extra work is done.

This would be called every 2.9ms. At a place, where i wrote assembler to save some us, and Paul mentionend assembler in his comments in the source.

I think that I do not want ...
 
so, why NOT? (I'm only curious)
"NOT" meaning "NOT statically allocate"...
In other words I'm saying NO to "This SWI is owned for FOO, and can't be used for BAR".
This would be the wrong way to "allocate" them.
The example code is how I would NOT allocate them to anything permanent.

Would it be possible to pass two pointers to the callback functions, one for context and one for data?
That would be pretty difficult to do.
The idea isn't that they can or can't be used as a callback. My use will be for bottom half processing of an ISR that depends on the ISR to be able to get another hit.
While it does this, it actually blocks user code while some other code executes, preventing usage collisions.
If written just right, you could point the SWI to a callback as long as you are familiar with the consequences.
Furthermore, without some special sauce, many functions can't be used... However I have code that lifts this limitation on anything that would use any sort of heap, except for sbrk(), but nobody should be using sbrk() in modern code anyway.
 
WMXZ, have a look at "free Rtos".

I only tried to stay within the scope of 'standard' T3.1 software, i.e. NO-OS, and allow other readers that have no complex system experience to appreciate the use and problems with interrupts and ISRs.

I, personally, have no problems with ISR programming, including deferred processing. Here, I consider the SWI topic an interesting playfield for others to understand the do and don'ts of interrupt processing.

OK, one could also use timers, but here we are talking about SWI.
 
I really think, a simple documentation "This lib uses that intnum" is ok.
The users are used to do this... they know that they can't use the same resource more than once.
Think of timers and other stuff on other mcu's.

We don't need the xzy's OS
 
WMXZ :) okay, then you know how to write your own example ! ;-) You can call every int you wish with NVIC_SET_PENDING(num);
 
Good that my T3.1 not know that he has only three. :)
He could not play mp3.

Maybe it is confusing this too ? :)
Maybe you meant the software-isr 110 ? But this is only one. What are you speaking of ?
 
Last edited:
oops...i think i have a little bug in my mp3lib.. :) 16 too much... i'll update it :)

Only to check:
in mp3lib you have for example in mp3aac.h

Code:
#define IRQ_AUDIO2		56

and in mp3aac.c

Code:
_VectorsRam[IRQ_AUDIO2 + 16] = decoder;

I guess it worked as both 56 and 72 are unused vectors
 
What is the purpose of swi() ?

Often, it is used to push the CPU registers on to the stack (state save) and jump to a certain handler. This is often used in RTOSes to invoke the task scheduler or some such thing.
 
Back
Top