cancel
Showing results for 
Search instead for 
Did you mean: 

Best way to mate interrupts with c++ periephal drivers?

JoniS
Senior

Hey!

Lets say I write general Can periephal driver in c++ for stm32f4 series(which I'm actually doing), then interrupt handlers for can1 and can2.

Now the proplem is how do I tell lets say can 1 rx interrupt, that >>this instance of object<< is the one which you should use to access correct circular buffer to fill.

What comes to my mind is that I could put X amount of static pointers inside the namespace but outside the periephal class object(to limit scope), and then in the constructor set correct address to the pointer, but is there better ways?

I'm not really fan of the Arduino way of copypasting the driver in different static objects, one for each periephal because that leads to hard to maintain very error prone code.(I assume they still do that)​

6 REPLIES 6
Piranha
Chief II

Probably in a similar way like people do it in C:

class_can_tc oCAN1;
 
void CAN1_RX0_IRQHandler(void)
{
	oCAN1.fnISR();
}

While this would indeed work, then one must Everytime use same name for the class instance or rename it from the ISR, which is something i want to avoid.

CanPeriephal can(params..);
 
can.UseReceiveInterrupt(true);
 

Basically i want such api, which would be easy and the RX interrupt would be seamlessly integrated no matter what I wanted to call the object instance.

The IRQ needs to know what class you’re using for that peripheral. It can’t automagically determine it. You could write a wrapper in which case you set a global pointer to that, but that seems just as inelegant as a global pointer in the first place.

If you feel a post has answered your question, please click "Accept as Solution".
berendi
Principal

Make all peripheral classes inherit from a common peripheral class with a virtual void irqhandler(int) member function. Then create a single global array where all peripherals can register their instance addresses.

class peripheral {
  public:
    virtual void irqhandler(int) = 0;
  /* ... */
}
 
struct irqdescriptor {
  peripheral * instance;
  int irqtype;
} irqarray[128];

The irqtype field is needed to differentiate when there are multiple hardware interrupts assigned to one peripheral, e.g. rx and tx for can, or event and error for i2c. Every peripheral object should be able figure out its irq number(s), and fill in its entries in the table with its this pointer.

Now each hardware interrupt handler should be able to look up the object pointer in the table, e.g. CAN1_TX_IRQHandler would call irqarray[19].instance->irqhandler(irqarray[19].irqtype). Better yet, you can redefine the Default_Handler instead of copy-pasting the same code to 90+ handler functions (if an interrupt handler is not defined in the user code, it would call Default_Handler)

void Default_Handler(void) {
  unsigned irqnumber = SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk;
  struct irqdescriptor *desc = irqarray + irqnumber;
  desc->instance->irqhandler(desc->irqtype);
}

This would work for most peripherals, except where a single interrupt request is shared among multiple peripherals, e.g. TIM1_UP_TIM10, so there would be a few special cases needing more work.

Don't forget that there are lots of restrictions on what an interrupt handler can do (or rather just a few things that a handler can do). All variables shared between a handler and normal (thread mode) code should be declared as volatile, and atomically accessible (in practice, 8, 16, and 32 bit integers only). Forget about using std::queue to store received frames in the interrupt handler, boost::lockfree::queue (or spsc_queue) might be safe, but don't take my word for it. No waiting for semaphores either. Use only functions that are explicitly documented as safe to call in a signal handler.

Don't forget the extern "C", otherwise your ISR will never be called 😉

FreeRTOS has ISR-safe queue insertion and other mechanisms required.

I use C++ for ISRs with FreeRTOS...