Interrupt handlers in startup_stm32l432xx.s etc

I’m looking for a way to squeeze out some more memory use from STM32 builds, in a general way for all devices with ≥ 8 kB RAM, which is the bare minimum for my project. At this point, I’m looking at the interrupt dispatch vector. I’m currently copying it to RAM, because the interrupt handlers may change at run time (e.g. DMA streams used for different devices at different times in the application). This works, but takes up in the order of 1 kB (4 bytes for the function pointer + 4 bytes for additional data, assuming some 100 vector entries).

My solution is to have a generic interrupt handler which looks up a single-byte “index” and uses that to further dispatch the interrupt, for roughly 85% saving in memory use. Which means that all the interrupts in the system (there can be many) now use the same code, and all the entries in the interrupt dispatch vector are identical. And indeed, this works as expected.

But the problem is that a CMSIS startup file such as startup_stm32l432xx.s has all interrupt vectors hard-coded, and the exact list differs for each of the (hundreds of) CPU variants. In its wisdom, CMSIS defines all handlers as “weak” references, so they can be overridden, and they are all set to use the Default_Handler, which is also defined in that startup file.

But I can’t override Default_handler, as it itself is not weakly defined. Any attempt to do so results in a “Default_handler multiply-defined” linker error. My options seem to be as follows:

  1. replace the CMSIS .s startup file with my own - drawback: it will need to be done for each CPU variant, so some kind of auto-generation would be needed
  2. create a source file with all the interrupt handlers defined as aliases to my own “Default_handler” - drawback: same issue, i.e. this source differs for each CPU variant

Re option 1, I don’t know how to do this in PlatformIO. The cmsis.py script in platforms/ststm32/ appears to be hard-coded to fetch the startup from the built-in CMSIS framework area.

Re option 2, one idea would be to simply generate all possible handlers in a source file, i.e. the union of all IRQ handler names found in all of STM’s CPU variants, Since there are some naming differences, that may well end up being a list of 200…300 functions (or rather: aliases to my own default handler), and then the linker would remove all unreferenced functions. But it feels like a hack …

So the question is: how to make all interrupts go through a single handler function, which then takes care of dispatching it its own way. In a way which will work across most boards …

Are there other ways to approach this? I looked at the linker’s --wrap option, but I couldn’t get it to do anything (e.g. -Wl,--wrap=Default_Handler).

To follow up, I went with the hack, it’s … doable:

extern "C" void irqDispatch () {
    auto vecNum = MMIO8(0xE000ED04); // ICSR
    auto idx = irqMap[vecNum];
    devMap[idx]->irqHandler();
}

#define IRQ(f) extern "C" void f () __attribute__ ((alias ("irqDispatch")));

IRQ( ADC1_IRQHandler                    )
IRQ( AES_IRQHandler                     )
IRQ( BusFault_Handler                   )
...

I can just add all IRQ handler names in there, no matter which STM32 µC it is. The linker will use those it needs and ignore the rest.

(This wasn’t really a PlatformIO-specific issue, BTW)

1 Like