I'm coding for Cortex M0/M4 at the moment and the approach we are using in C++ (there is no C++ tag, so this answer might be off-topic) is the following:
We use a class CInterruptVectorTable
which contains all the interrupt service routines which are stored in the actual interrupt vector of the controller:
#pragma location = ".intvec"
extern "C" const intvec_elem __vector_table[] =
{
{ .__ptr = __sfe( "CSTACK" ) }, // 0x00
__iar_program_start, // 0x04
CInterruptVectorTable::IsrNMI, // 0x08
CInterruptVectorTable::IsrHardFault, // 0x0C
//[...]
}
The class CInterruptVectorTable
implements an abstraction of the interrupt vectors, so you can bind different functions to the interrupt vectors during runtime.
The interface of that class looks like this:
class CInterruptVectorTable {
public :
typedef void (*IsrCallbackfunction_t)(void);
enum InterruptId_t {
INTERRUPT_ID_NMI,
INTERRUPT_ID_HARDFAULT,
//[...]
};
typedef struct InterruptVectorTable_t {
IsrCallbackfunction_t IsrNMI;
IsrCallbackfunction_t IsrHardFault;
//[...]
} InterruptVectorTable_t;
typedef InterruptVectorTable_t* PinterruptVectorTable_t;
public :
CInterruptVectorTable(void);
void SetIsrCallbackfunction(const InterruptId_t& interruptID, const IsrCallbackfunction_t& isrCallbackFunction);
private :
static void IsrStandard(void);
public :
static void IsrNMI(void);
static void IsrHardFault(void);
//[...]
private :
volatile InterruptVectorTable_t virtualVectorTable;
static volatile CInterruptVectorTable* pThis;
};
You need to make the functions which are stored in the vector table static
because the controller cannot provide a this
-pointer as the vector table is not an object. So to get around that problem we have the static pThis
-pointer inside the CInterruptVectorTable
. Upon entering one of the static interrupt functions, it can access the pThis
-pointer to gain access to members of the one object of CInterruptVectorTable
.
Now in the program, you can use the SetIsrCallbackfunction
to provide a function pointer to a static
function which is to be called when an interrupt happens. The pointers are stored in the InterruptVectorTable_t virtualVectorTable
.
And the implementation of an interrupt function looks like this:
void CInterruptVectorTable::IsrNMI(void) {
pThis->virtualVectorTable.IsrNMI();
}
So that will call a static
method of another class (which can be private
), which then can contain another static
this
-pointer to gain access to member-variables of that object (only one).
I guess you could build and interface like IInterruptHandler
and store pointers to objects, so you don't need the static
this
-pointer in all those classes. (maybe we try that in the next iteration of our architecture)
The other approach works fine for us, as the only objects allowed to implement an interrupt handler are those inside the hardware abstraction layer, and we usually only have one object for each hardware block, so it's fine working with static
this
-pointers. And the hardware abstraction layer provides yet another abstraction to interrupts, called ICallback
which is then implemented in the device layer above the hardware.
Do you access global data? Sure you do, but you can make most of the needed global data private like the this
-pointers and the interrupt functions.
It's not bulletproof, and it adds overhead. You'll struggle to implement an IO-Link stack using this approach. But if you're not extremely tight with timings, this works quite well to get a flexible abstraction of interrupts and communication in modules without using global variables which are accessible from everywhere.