Could there be something like an ISR template function?

christoph

Well-known member
I'm writing a template class that uses interrupts. For every specialization, I have to make sure that the ISR name matches the template arguments. That's tedious and error-prone.

I'm not trying to change the way teensyduino works, but would it be theoretically possible to create an interrupt handler table with specialized template functions? The "classic" C table looks like this:
Code:
__attribute__ ((section(".vectors"), used))
void (* const gVectors[])(void) =
{
  (void (*)(void))((unsigned long)&_estack),	// 0 ARM: Initial Stack Pointer
  ResetHandler,	// 1 ARM: Initial Program Counter
  nmi_isr,
  ...

Instead, we could move the whole table out of the "extern C" declaration and use a template function:
Code:
template<typename T>
void handler(void)
{
  ... // what the "unused_isr" handler does
}

void (* const gVectors[])(void) =
{
  (void (*)(void))((unsigned long)&_estack),	// 0 ARM: Initial Stack Pointer
  Handler<Reset>,	// 1 ARM: Initial Program Counter
  Handler<Nmi>,
  ...

And specialize ISRs in headers:
Code:
template<>
void Handler<Dma::Channel<3> > (void)
{
  ...
}

That would facilitate e.g. passing a DMA channel as a template parameter to a driver class, which then specializes the handler for this DMA channel interrupt. I'm just not sure if it *can* work, that's why I didn't try yet. Any thoughts on this?

Regards

Christoph
 
The NVIC is a form of such table. I think an issue is a lack of a means to do run-time request-return NVIC slots among legacy programs and core code that use interrupts.
Just managing who wants a slot and accepting now-unused slots would help and not require templates and so on to be coded everywhere.

however the interrupt vector management would be done, there's the other resources:
contention for SPI, I2C.
DMA channels

there's a fine line between trying to get regularity in these, and being a core operating system.
I suppose that line is when the MCU is run in supervisor/user mode so that user apps cannot directly access on-chip resources.
That leads to a operating system and that's not where simplicity-minded folks wanna go.

It's perplexing and, e.g., the SPI sharing issue is being widely debated here and in Arduino-land.
 
Last edited:
You missed my point here - I want to be able to write an ISR, and that's it. I just don't want to do that in an implementation file, but in a header, and pick the ISR name without typing it myself and without using macros. This is not at all about managing access to a peripheral, I know those debates.

Regards

Christoph
 
Oh. I thought you were proposing a universal template and have a table of interrupt sources and service routines. And a general class for interrupt handlers.
 
No, I'm proposing a different way of implementing what is currently in use. Having a template function for interrupt handlers is a universal thing, as you say, and it could provide a default implementation (we also have that) for unused ISRs.

However, as I have indeed used the word class in my proposal, this does not impose any restrictions on the implementation of any peripheral class - they just need to be declared, and any driver can implement them as desired. The definition can (and should) be done by special driver libraries, and that's where my proposal ends and the "how do we manage access to the SPI" -discussion begins.
 
Basic Example:

handlers.h forward declares peripherals, an interrupt handler template with default implementation, and an array of handlers:
Code:
#ifndef HANDLERS_H
#define HANDLERS_H

#include <iostream>

/** Peripherals: Forward Declarations **/
class SPI;
// ... more peripheral forward declarations would follow

/** ISR template with default implementation **/
template<typename T>
void intHandler(void)
{
  // default handler
  std::cout << "default handler\n";
}

/** ISR table **/
extern void (* const handlers[])(void);

#endif // HANDLERS_H

The implementation file just defines the handler array (this would be done in a fictional mk20dx128.cpp):
Code:
#include "handlers.h"

void (* const handlers[])(void) =
{
  intHandler<SPI>,
  intHandler<I2C>
};

Now we need some SPI driver that uses interrupts. Note that we do not need an implementation file for the ISR any more:
Code:
#ifndef SPILIBRARY_A_H
#define SPILIBRARY_A_H

#include <iostream>

#include "handlers.h"

class ObscureSpiDriver
{
  public:
    static void handleInterrupt(void)
    {
      std::cout << "ObscureSpiDriver::handleInterrupt()\n";
    }
};

template<>
void intHandler<SPI>(void)
{
  ObscureSpiDriver::handleInterrupt();
}

#endif // SPILIBRARY_A_H

main.cpp includes the handlers, the SPI library and calls the two available handlers:
Code:
#include <iostream>

#include "handlers.h"

#include "spiLibrary_A.h"
//#include "spiLibrary_B.h"

int main()
{
  handlers[0]();
  handlers[1]();
  return 0;
}

Output:
Code:
ObscureSpiDriver::handleInterrupt()
default handler
If a second SPI library is included in main.cpp which also defines an SPI interrupt handler, the compiler will complain about a redefinition (this is currently the linker's job).

I'll write a second example that actually allows choosing a DMA channel with a template parameter. That's great when we want to give DMA channel 0 to library A and DMA channel 1 to library B.

Regards

Christoph
 
I'm very interested in the DMA interrupts thing. I'm implementing a DMA buffer for the ADC library and I'd like it to be possible for the user to select which DMA channel to use, so that it doesn't conflict with other libraries. So far the only problem left I have is with the interrupts.
I was thinking about something similar to what you've written, but I didn't really know how to put my thoughts into code.
 
I'm very interested in the DMA interrupts thing. I'm implementing a DMA buffer for the ADC library and I'd like it to be possible for the user to select which DMA channel to use, so that it doesn't conflict with other libraries.

I've thought about this DMA conflict issue several times and considered adding something in the core library for DMA channel allocation.

I've considered 2 possible ways.

#1: Runtime allocation by calling a well defined function that maintains a list of DMA channels in use by all libraries. It would simply return an integer of the next available DMA channel. Libraries using it would need to use that runtime defined integer and a pointer to the TCD structure rather than compile time constants and hard-coded registers.

#2: Linker allocation by creating a special linker section in mk20dx256.ld. Libraries needing a DMA channel would create variables with an attribute to place them in that special section. Then their addresses could be used as the DMA channel number. This would probably all be wrapped in preprocessor macros to hide the ugly syntax.

Maybe this needs to be a new thread, just about DMA channel allocation. Today there's only a few libraries using DMA, but over time these conflicts will grow. I'm open to ideas on this DMA issue, so please speak up if you'd prefer something else?

To include this in the core library, it must be usable from both C++ and C-only code.
 
Paul, why must it be usable from C code? C rules out all template trickery, so we're stuck with either macros, runtime allocation or linker stuff.
 
In the ADC library I'm doing a runtime allocation thing. I have pointers that increment the correct amount to point to the correct DMA channel registers. The only problem left is how to access the isr. Is it possible to do something like
Code:
dma_isr_vector[]={dma_ch0_isr, dma_ch1_isr, ...}
and then have them point to a function like?
Code:
dma_isr_vector[channel]=function;
By default the library will assign channels 0 and 1 to the buffers (if there's an ADC1 present), but the user can change this before starting the dma.

I think that the core library should have a simple list of the channels that are free or in use and simple functions to reserve and free them.

An other issue with DMA is the priority stuff, I think that the higher the channel number the higher the priority, but maybe it should be changed to a round-robin style? or maybe is convenient to have different priorities for time sensitive libraries. Something like:
Code:
uint8_t reserveDMA(uint8_t priority)
And priority from 0 to 255 or something like that.
 
Paul, why must it be usable from C code?

Because a very substantial number of people wish to program only in C.

Later this year, I'm planning to expand the Teensy 3.x software to 8 downloads, the 4 Teensyduino ones we have today, and 4 more non-Arduino downloads intended for compiling by Makefiles or integration with IDEs.

Support for use without touching Arduino is probably the most requested feature (now that a lot of other highly requested things have been implemented). A portion of those requesting non-Arduino support also wish to program without using C++. They'll lose a LOT of stuff, but it still needs to be possible.

I do not wish to repeat my mistake with Teensy 2.0, where the standalone, C-only code stagnated while the Arduino code continued development. If you look through the teensy3 core library, you'll see nearly all the code is C-only, with very light C++ wrappers. For example, look at HardwareSerial.h. This is all part of my plan, from the very beginning of Teensy 3.0, to keep it all in a single well maintained code base.

Of course, it's been about 1.5 years with only Arduino style releases. A standalone, non-Arduino release is long overdue.

I know C++ templates provide some awesome capability. Sadly, they can't be used for this. This core platform stuff needs to be sharable between C and C++, so C++ only features like templates just aren't acceptable.
 
Go ahead, but I think we won't be able to keep those discussions separated, because the DMA channels each have their own ISR.
 
IMO, the DMA discussion probably will involve IRQ handling, just because all manner of systems can use DMA, so it will by necessity require a callback function capability. In other words, it needs to not only hand out channels, it needs to vector the DMA interrupts to the target handler. That stuff should be wrapped in a common core library.
 
How about a discussion on github? We could create feature request issues and discuss each of them in a separate issue discussion. Agreed upon requirements would go into a master document, and then we can start working on the code.
 
Yes but we have far more interrupt sources than only dma. for timers, this could be interesting too.
How about a low level function "updateISR()" that is able to change the isr for all interrupts at runtime?
 
Yes, we do have far more indeed and many of them probably deserve getting a better management. I would take DMA as a first candidate, see how that should be managed and then decide how we can implement a good management for other perpiherals. We won't find a "one-fits-all" implementation in the first try and we will learn a lot even by just tackling DMA first. That would certainly speed up similar efforts for other peripherals.
 
Yes but we have far more interrupt sources than only dma. for timers, this could be interesting too.
How about a low level function "updateISR()" that is able to change the isr for all interrupts at runtime?

I don't think it makes sense to have dynamic IRQs for all interrupts. It would just be an unnecessary overhead, and some IRQs are already too slow (for instance nobody is going to dynamically change the USB handler on-the-fly).

However for some systems it makes sense. IntervalTimer (PIT interrupt) already works this way. The library will assign a PIT channel number behind the scenes and you only need to give it a handler function and an interval time. I would think a model like that would also work for the DMA, but I don't know enough of the various DMA modes to know if it can work for all cases.
 
I don't think it makes sense to have dynamic IRQs for all interrupts.
I do, and have used them. Mainly one-time during initialization and instancing of classes.
If resources are scarce and a resource (IRQ vector/NVIC) or DMA channel, or multi-purpose ports like SPI are shared, it's needed at run-time. E.g., I need SPI for a moment for the SD card, or to access a sensor infrequently.
 
I do, and have used them. Mainly one-time during initialization and instancing of classes.
If resources are scarce and a resource (IRQ vector/NVIC) or DMA channel, or multi-purpose ports like SPI are shared, it's needed at run-time. E.g., I need SPI for a moment for the SD card, or to access a sensor infrequently.

Well I absolutely don't agree with that. Frankly the pin IRQ's are already too slow, and another layer of clock consuming redirection in core would just mightly annoy the hell out of me. I would have to create my own Teensyduino or just jump to another chip altogether.

You are describing the corner-case where you don't have enough memory to keep a SPI class instantiated, and need to create it on-the-fly. I think this is pretty far removed from what most people need, and to slow down everything to accommodate it is IMO wrong.
 
No, not via a redirect. I believe we can copy the interrupttable to ram and use it there. It would be a little bit faster since we have not waitstates as with flash. Downside is the ram usage.
 
If there was a workaround to swap ISRs with no real time cost (to the normal case), then yes I would be all for that.

I'm not familiar with executing code from ram, however on other parts I've worked with the IRQ vector table was in ram. I've always figured the vector table is in flash here (not sure why, maybe that is wrong). If it is in ram, then yes if there were a way to say change the vector to point to different handlers then I would be all over that (it would solve problems I have right now).
 
Back
Top