Mastering interrupts is critical to making an embedded application reentrant. The challenge with reentrancy is that things might be implemented in a wrong way and the issue might just show up sporadically (see “EnterCritical() and ExitCritical(): Why Things are Failing Badly”). The ARM Cortex interrupt controller is named NVIC (Nested Vectored Interrupt Controller).
As the ‘nested’ in NVIC indicates, that controller supports nested interrupts which is a good thing from an interrupt latency and flexibility perspective. I can use the NVIC to selectively disable/enable interrupts. If done properly, I don’t have to disable the interrupts system-wide: I can narrow down my interrupt locking to a minimum of interrupts.
The NVIC has the following registers to enable/disable interrupts (one bit for each vector number):
- NVIC_ISER: Interrupt Set Enable Register to enable an interrupt source
- NVIC_ICER: Interrupt Clear Enable Register to disable an interrupt source
- NVIC_ISPR: Interrupt Set Pending Register to raise an interrupt
- NVIC_ISCR: Interrupt Clear Pending Register to clear a pending interrupt
The following shows e.g. the bits to enable DMA interrupts on a Freescale KL25Z device:
To disable an interrupt source, I can do this in the following CMSIS way:
NVIC_DisableIRQ(device_IRQn); // Disable interrupt
with the right IRQ number. However, there is a possible problem with the architecture.
:idea: It is a false thinking that with these modern processors things will be ‘done immediately’: because of the internal pipelining, caching, busses and propagation delays settings will not have an immediate impact. Instead, there might be some delay.
So if an interrupt is just happening before the disable instruction, it might still happen after I have disabled the interrupt:
That might or might not be a problem for my design. But if I want to create a critical section to prevent that an interrupt is happening that way, I need to add ‘memory barriers’ instructions (see http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHEBHFF.html):
- DSB: Data Synchronization Barrier. Ensures that all explicit data memory transfers before the
DSBare completed before any instructions after the
- ISB: Instruction Synchronization Barrier. Ensures that the effects of all context altering operations prior to the
ISBare recognized by subsequent instructions. This results in a flushing of the instruction pipeline, with the instruction after the
ARM recommends first to use a DSB, followed by an ISB:
NVIC_DisableIRQ(device_IRQn); // Disable interrupt __DSB(); __ISB();
Memory barrier instructions are necessary if I don’t want to have a pending interrupt triggered, or if need to access the something in the peripheral space which is related to the interrupt source, e.g. changing the interrupt vector or a peripheral setting which for example would change the vector location. Or in other words where any interrupt activity of that peripheral would be a problem.
Not thinking through the fact that there are propagation delays in the ARM Cortex M0/M4 architecture can lead to flawed interrupt handling. The nasty thing is that the problem will occur only rarely, and it will be hard to track down. Adding a memory barrier might be the golden bullet to solve your problem too :-).
Happy Interrupting :-)