The Cortex-M0 processor from ARM is one of the most popular 32-bit embedded processors used in various microcontroller units (MCUs) and system-on-chips (SoCs). As an ultra-low power processor, it is well-suited for resource constrained and battery operated devices. One of the key features of Cortex-M0 is its interrupt handling mechanism which allows efficient asynchronous processing of events. However, enabling and properly configuring interrupts on Cortex-M0 requires good understanding of the processor architecture and programming model.
A common issue developers face when starting to work with Cortex-M0 interrupts is getting the ‘interrupt_demo’ code example to work properly in simulation. The interrupt_demo example demonstrates registering a handler for the SysTick timer interrupt and toggling an LED upon interrupt assertion. However, the LED toggle is often not observed in simulation even when the SysTick interrupt is triggered. There are a few reasons why this issue may occur:
Interrupt Handler Not Registered
The first step in enabling any interrupt on Cortex-M0 is to register an interrupt handler. This is done by populating the vector table with a function pointer for the specific interrupt. For SysTick, the vector number is 15 so the following code is used:
void SysTick_Handler(void) {
// toggle LED
}
void (*vector_table[16])(void) = {
//...
SysTick_Handler // entry 15 for SysTick
};
If the interrupt handler is not registered properly in the vector table, the processor will not know which function to execute when the SysTick interrupt occurs. Double check that the right vector table index is used and the function name matches the handler.
Interrupt Not Enabled
After registering the interrupt handler, the next step is to enable the SysTick interrupt in the Nested Vectored Interrupt Controller (NVIC). This is done with the following register write:
NVIC->ISER[0] = 1 << SysTick_IRQn;
This sets the enable bit for the SysTick interrupt in the NVIC. If this enable bit is not set, the NVIC will ignore the SysTick interrupt even if it occurs. Make sure the correct ISER register and bit shift is used to enable the SysTick IRQ.
SysTick Timer Not Started
The SysTick timer peripheral must be configured and started to generate the interrupt events. This requires writing to the SysTick Control and Reload registers:
SysTick->LOAD = 48000000; // 1 sec period at 48 MHz
SysTick->VAL = 0; // clear current value register
SysTick->CTRL = 5; // enable timer with interrupts
If the SysTick peripheral is not initialized correctly, it will not assert interrupts at the desired rate to trigger the LED toggle. Verify the timer reload, current value, and control registers are configured properly.
Priority Masking
Cortex-M0 supports priority grouping and masking to allow fine-grained control over interrupt preemption. This needs to be considered when multiple interrupts with different priorities are used.
// Set priority grouping format
NVIC_SetPriorityGrouping(3);
// Set SysTick priority
NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(3, 2, 0));
If a higher priority interrupt occurs while the SysTick handler is running, it will pause SysTick interrupt handling. Make sure the SysTick priority is set appropriately compared to other active interrupts.
Fault Exceptions
On Cortex-M processors, faults and exceptions will take priority over handler mode interrupts. Unhandled faults like memory access violations, divide by zeros, etc can pause execution of the SysTick interrupt.
The HardFault_Handler can check the HFSR register to debug the cause of faults:
void HardFault_Handler(void) {
volatile uint32_t hfsr = SCB->HFSR;
// report and handle fault
}
Proper error checking and handling needs to be set up to prevent/recover from faults for interrupt processing to continue.
Debug Halting
When debugging and single stepping through interrupt code, the debugger will often pause execution on the target processor. This halting of the core will stop timer interrupts from occurring until execution resumes.
Stepping through the interrupt handler slowly without resetting the timer peripherals can make it appear interrupts are not working. Allow the processor to run freely when validating interrupts.
Race Condition with Main Loop
Depending on the timing between the main program loop and the interrupt handler execution, a race condition may prevent the interrupt from toggling the LED.
For example, if the main loop turns the LED off right after the interrupt turns it on, the on state may not be visible. Introducing a short delay in the main loop can avoid this race condition.
while (1) {
LED_Off(LED1);
Delay_ms(5); // short delay
}
Improper Simulation Model
The system simulation model may not match the actual MCU device functionality accurately. So interrupt behavior in simulation does not correspond to real silicon.
Using single step debugging on real hardware can help validate if the interrupt handler is being executed correctly before trying in simulation.
Conclusion
There are a variety of reasons why interrupts may not appear to work properly in Cortex-M0 simulations. Double checking the handler registration, interrupt enables, timer configurations, priority settings, fault handling, and race conditions can help troubleshoot the issue. Proper modeling and matching the simulation to actual MCU functionality is also important.
Identifying and fixing these common interrupt problems enables developers to take full advantage of the asynchronous processing capabilities of Cortex-M0 in their embedded applications.