What to do, when interrupt is enabled in a program, but the Interrupt Service Routine (ISR) does not run?
As usually, the most straighforward method is to follow the path of execution in hardware, i.e. in the mcu itself. This is contrary to the usual wishes of the novices, who don't want to go beyond the comfort of the IDE and software-writing process, using some sort of "libraries". Sorry, but that's not how it works, the mcu does not care about libraries nor software.
One more "inconvenience" is, that successful debugging requires understanding of the underlying mechanisms, which is achieved by reading the available documentation. For interrupts, this involves the Reference Manual (RM), mainly Interrupts and Events chapter (in some newer RM named differently, or even split e.g. into Nested vectored interrupt controller (NVIC) and Extended interrupts and events controller (EXTI) chapters); plus the chapter related to the particular peripheral from which the particular interrupt signal originates. However, the real working of NVIC is not described there, as it is part of the processor/core, so the related Programming Manual (PM) has to be consulted (and perhaps other related ARM material). And, note, that from ARM's point of view, interrupts fall into the somewhat broader group of "exceptions", so you want to read everything about "exception priorities" there.
As usually, the most convenient tool to use is the built-in debugging facility plus the associated external hardware (debugging "pod") and software (often IDE, sometimes bare gdb with associated interfacing layer (like openOCD) or other similar tools). The following lines are written mostly with this method in mind. However, as usually, this is not a necessity, the good programmer has a whole range of tools and methods at hand - some of them avoiding the issue that the original problem may actually be caused by the built-in debugger being intrusive to a certain extent.
Here are some steps to contemplate:
- Do you properly check that the ISR does not run? Place a breakpoint directly into beginning of the ISR (not just into any "callback" from a "library", or any other secondary function call). Maybe better, toggle a pin at that place, observing it using an oscilloscope or logic analyzer (LA).
- Are other interrupts at this point running successfully? If yes, that excludes some modes of failure (e.g. global interrupt disable).
- Check, that the desired interrupt is enabled at its source, in the peripheral's registers.
- Check in the peripheral's status bits, that the desired state leading to interrupt has indeed been achieved (don't forget, that in some peripherals (e.g. SPI and UART Rx), observing registers using debugger may actually clear the desired interrupt).
- Check in NVIC_ISERx, that the given interrupt is enabled. If not, check if you've called
NVIC_EnableIRQ()
with the proper interrupt number. - Check - best in mixed C/disasm view - that the proper vector to the ISR is inserted to proper position inside the interrupt vector table. If not, check if the ISR name matches exactly (noting that C is case sensitive) that in the vector table (usually in the startup code; the interrupt chapter in RM contains the reference list of interrupt vectors)1.
- Check, that SCB_VTOR points to the proper interrupt vectors table.
- Aren't interrupts disabled globally? By default, they are not, so watch out for any occurence of
__disable_irq()
(orCPSID I
) in your program (it acts on a special-purpose ARM register, PRIMASK, some debuggers can display its content, too). - Isn't the program being stuck an interrupt with the same or higher priority? This should be easy to find out by stopping the program and observing where it currently executes; or observe SBC_ICSR.VECTACTIVE.
Also note, that the debugger will usually prevent interrupts being invoked, while single-stepping.
1. Users of C++ often don't realize, that in C++ function names are mangled by the compiler, so the linker cannot match the name in the vector table to the user-written ISR and falls back to the default "weak" handler, usually implemented as an empty infinite loop. The canonical way to avoid name mangling in C++ is to enclose the given function (ISR) into extern "C"{} block:
extern "C" { void TIM2_IRQHandler() { [...] } }