C++ interrupt handling

23 Nov 2010

In addition to the many fast prototyping benefits I found using mbed, I have really enjoyed the use of C++ to elegantly produce embedded source code - creating layers of abstraction.  The TimerEvent and derived classes are especially interesting and impressive.

As I move from prototyping into a production phase, using local tools source code, I'm having a little trouble figuring out the best way to register C++ member functions with the interrupt controller.

Without necessarily needing to see the whole source for the TimerEvent, and Ticker classes, could someone please explain the general process for connecting a hardware interrupt to a member function?

I'm pretty sure I understand the linked list of registered events concept, but I'm not following how the member function

 // The handler registered with the underlying timer interrupt
static void irq();
is actually called, and what irq() actually does.

It makes sense that a static function is required, but I'm wondering how many layers of abstraction are used when trying to maintain the performance given by the Cortex-M3 NVIC.

I would really appreciate some additional insight into how the interrupt logic implemented in the mbed library works such that I could leverage some our prototype code onto our production platform.

Thanks.

24 Nov 2010

Hi Richard,

To call a C++ member function, you need the object to call it on (as I think you understand). This pointer is some state that you need to have stored somewhere. How you do that can be very dependant on what you are trying to achieve, and particularly on how much can be implicitly defined in your code.

A static member function doesn't have a this pointer, as it is not associated with any particular instance, and hence you can call it like any other global/static function.

As a simple example, you could do something like this to allow you to call handlers on two different instances of an object:

#include "mbed.h"

class Foo {
public:
    void handler() {}
};

Foo* handlers[2];

extern "C" void WHATEVER_IRQHandler() {
    int id = rand() % 2; // determine what to call
    handlers[id]->handler();        
}

Foo f0;
Foo f1;

int main() {
    handlers[0] = &f0;
    handlers[1] = &f1;
    
    // setup WHATEVER interrupts
    while(1); 
    
}

Obviously, instead of rand(), you'd be reading some IRQ or peripheral registers to determine what to do. Or maybe you only have one class, so you can just call it.

Now, you could then wrap that up all in to a class, so the constructors do the registration. As another example, i've also dynamically setup the vector if they get instanced:

#include "mbed.h"

class Foo {
public:
    Foo(int id) {
        handlers[id] = this; // register this instance against the id
        NVIC_SetVector(WHATEVER, (uint32_t)&irq); // setup, enable etc...
    }
    
    void handler() {} // do something specific to this object instance
    
    static void irq() {  // work out which object to call
        int id = rand() % 2; // determine what handler to call
        handlers[id]->handler();
    }
    
    static Foo* handlers[2];
};

Foo* Foo::handlers[2] = {0};

Foo f0(0);
Foo f1(1);

int main() {
    // setup interrupts etc
}

If you want to have classes that can register other classes, or you want to be able to register different methods, then things can get more complicated! (e.g. the mbed library has to be pretty generic to handle lots of different requirements), but in general, the approaches above are what I expect you'll find fits your model.

Hope this helps, Simon

24 Nov 2010

Yes that helps a lot.  Just to make sure I have this correct, you cannot create a static member function named SysTick_Handler that overrides the [WEAK] defined dummy handler in the standard startup.s file just using the linker.  The linker will not override the dummy handler, and this will never get called by a SysTick interrupt:

class Foo {
  static int time;

public:
  static void SysTick_Timer();
};

extern "C" void Foo::SysTick_Timer() {
  time++;
}
The proper way is to define a stand-alone static function outside of a class, as you did in your 1st example, that can then call a static member function or a non-static member function of a particular object.  Is the dynamic assignment with NVIC_SetVector() the only way to define an IRQ Handler as a static function declared inside a class?

Is there any penalty in calling multiple functions to service an interrupt?  Does the process you've described negate any of the benefits of the Cortex M3 NVIC hardware feature in terms of instructions or resources related to entering and exiting interrupt mode?

 

Thanks again.  I agree the mbed library definitely needs to be more generic than a production system.  It's clear you all have put a lot of thought and work into the library.  You have enabled prototyping by others who might not otherwise be interested in software development - and as a plus, when an agreed upon prototype design is handed over, I'm hoping most of the code will be highly portable since hardware abstractions like the Ticker allowed them to separate control logic from hardware (no for loops for delays or register level configurations).  If I can write a Ticker class and a few others tailored to our specific layout (still using the NXP1768 for now), the high level prototype code might almost be cut-and-paste.

Is there an appropriate place on the mbed project pages to document any special challenges or tips related to de-generalizing some of the mbed library features for less generic use?  I know there has been a lot of discussion on the library source code and "offline compiling", but somewhat parallel to that (or perhaps even more appropriate), it might be interesting to cooperatively work with the header files currently published and implement the relevant source for less generic scenarios - if that seems in the spirit of what you are trying to do.  I'm thinking of a less generic scenario like an M3 micro on a fixed layout, so the pins and external devices aren't changing, and the peripheral setup is pre-defined.  What makes the API you have created still very interesting for that situation is the portability offered by abstraction.  That specific implementation could avoid things like classes registering other classes or dynamic interrupt mapping.  So perhaps a Ticker class gets much simpler then with a fixed number or even precisely defined functions called by a timer interrupt.  But then you take a working LED sequence or something that you were really happy with on your mbed platform, and now it's working the same way on your fixed layout (and maybe even on a different M3 part).  Does documenting that process sound like something within the scope of what the mbed project is trying to do?  In some ways that documentation could be more valuable to implementors than just posting the source.  I would be happy to contribute.

Thanks again, Richard

24 Nov 2010

Richard,

Building on what Simon said, have a look at MODDMA I just published. It uses the simple method Simon outlined. Because you can only have one instance of the Controller there's just single class pointer (init to NULL so the IRS knows an instance exists). When the single instance constructor is invoked it just writes this into the global pointer so the IST can call methods within it.

However, I'm lucky, MODDMA as a controller only has a single instance so only needs a single pointer. In Simons examples you could use an array of pointers but that assumes you know how many objects you will need to make callbacks on. Further than that your looking at a dynamic linked list type of container.

But once you have established a link back from the ISR to at least your base object, the Mbed library's FunctionPointer system then makes life simple making callbacks from that object to others that regsiter (.attach()) themselves. MODDMA makes big use of Mbed FunctionPointers for it's base callbacks and additionally the per configuration callbacks.

--Andy