SoC
  • Home
  • Arm
  • Arm Cortex M0/M0+
  • Arm Cortex M4
  • Arm Cortex M3
  • Contact
Reading: Use the same ISR for multiple interrupt sources in Cortex M0+
SUBSCRIBE
SoCSoC
Font ResizerAa
  • Home
  • Arm
  • Arm Cortex M0/M0+
  • Arm Cortex M4
Search
  • Home
  • Arm
  • Arm Cortex M0/M0+
  • Arm Cortex M4
Have an existing account? Sign In
Follow US
  • Looking for Something?
  • Privacy Policy
  • About Us
  • Sitemap
  • Contact Us
© S-O-C.ORG, All Rights Reserved.
Arm

Use the same ISR for multiple interrupt sources in Cortex M0+

Neil Salmon
Last updated: October 5, 2023 9:58 am
Neil Salmon 8 Min Read
Share
SHARE

The Cortex M0+ processor supports handling multiple interrupt sources using a single interrupt service routine (ISR). This can help reduce code size by avoiding duplicated ISR code. The key is configuring the Nested Vectored Interrupt Controller (NVIC) to route multiple interrupt sources to the same ISR function.

Contents
Benefits of Sharing an ISRConfiguration StepsIdentifying the Interrupt SourceCheck Peripheral Interrupt FlagsUse Device Vector TableUse Interrupt Priority LevelAssociating Data with Interrupt SourcesAccess Global VariablesUse Function PointersCreate Interrupt Context StructuresPrioritizing InterruptsInterrupt Latency ConsiderationsUsage ExamplesUART + Timer InterruptsMultiple ADC Channel ConversionMotor Control ISRConclusion

Benefits of Sharing an ISR

There are several benefits to using a single ISR handler for multiple interrupt sources on Cortex M0+:

  • Reduces overall code size since the ISR code only needs to be defined once.
  • Simplifies interrupt handling code.
  • Allows flexibility to add/remove interrupt sources easily by modifying NVIC routing only.
  • Avoids duplicated effort when handling similar peripheral interrupts.

For resource constrained Cortex M0+ devices, minimizing code size is often a priority. Sharing an ISR can help reduce Flash usage and leave more room for application code.

Configuration Steps

To configure multiple interrupts to share an ISR handler on Cortex M0+ involves a few simple steps:

  1. Define a single ISR handler function that will be shared. This takes the standard form:
  2. In the NVIC peripheral registers, configure the interrupt sources to route to the same IRQ channel.
  3. Enable interrupts for the desired sources by setting the enable bits in the NVIC peripheral.
  4. Write the shared ISR handler code to check which source caused the interrupt, and handle appropriately.

As long as the interrupts are routed to the same IRQ channel, the shared ISR will execute when any of the enabled sources trigger.

Identifying the Interrupt Source

Within the shared ISR, we need a way to identify which source triggered the interrupt. There are a few ways to achieve this on Cortex M0+:

Check Peripheral Interrupt Flags

Many peripherals have pending interrupt flags that get set when an interrupt occurs. Within the ISR, check the status of each peripheral’s interrupt flags:

void Shared_ISR(void) {

  if (TIMER1_IRQFlag) {
    // Handle Timer1 Interrupt
    TIMER1_ClearIRQFlag(); 
  }

  if (ADC_IRQFlag) {
    // Handle ADC Interrupt
    ADC_ClearIRQFlag();
  }

}  

This approach works well if the peripheral flags are directly accessible or easy to check.

Use Device Vector Table

The Cortex M0+ vector table contains the address of the active interrupt service routine. Within the shared handler, this can be compared to known ISR addresses to identify the source:

#define TIMER1_ISR     ((void*) 0x20004) // Timer1 ISR Address
#define ADC_ISR        ((void*) 0x20008) // ADC ISR Address

void Shared_ISR(void) {

  if (ACTIVE_ISR_ADDRESS == TIMER1_ISR) {
    // Handle Timer1 Interrupt 
  }

  if (ACTIVE_ISR_ADDRESS == ADC_ISR) {
    // Handle ADC Interrupt
  }

}

This takes advantage of the vector table routing to identify interrupts. However, it requires knowing the specific ISR addresses.

Use Interrupt Priority Level

The NVIC allows setting unique priority levels for each IRQ channel. Inside the shared handler, reading the current priority can help distinguish sources:

void Shared_ISR(void) {

  if (CURRENT_PRIO == TIMER1_PRIO) {
    // Handle Timer1 Interrupt
  }

  if (CURRENT_PRIO == ADC_PRIO) {   
    // Handle ADC Interrupt
  }

}  

This requires configuring the NVIC priority registers differently for each interrupt source during setup.

Associating Data with Interrupt Sources

Some applications require passing data or context along with an interrupt. There are a few techniques that can achieve this with a shared Cortex M0+ ISR:

Access Global Variables

Define global variables that can be set by main application code before servicing each interrupt:

volatile uint32_t int_data;

void main() {

  int_data = ADC_values; 
  NVIC_TriggerADCInt();

  int_data = Timer_count;
  NVIC_TriggerTimerInt();

}

void Shared_ISR() {

  if(ACTIVE_ISR == ADC_ISR) {
    // Use int_data
  }

}

This allows passing data to the ISR through a global variable. Context is determined based on which interrupt triggers.

Use Function Pointers

Set a global function pointer from the main code before servicing each interrupt. The ISR can then call the function:

void (*int_handler)(void);

void main() {

  int_handler = HandleADCInt;
  NVIC_TriggerADCInt();

  int_handler = HandleTimerInt;
  NVIC_TriggerTimerInt();  

}

void Shared_ISR() {

  int_handler(); // Call function based on context

}

This is useful for passing entire functions or code callbacks to the ISR.

Create Interrupt Context Structures

For more complex data, structure context variables can be defined:


typedef struct {
  uint32_t* data; 
  uint16_t length;
  void (*callback)(void);
} ISR_Context;

ISR_Context ctx_adc, ctx_timer;

void main() {

  ctx_adc.data = ADC_values;
  ctx_adc.length = 100;

  ctx_timer.data = Timer_ticks;
  ctx_timer.length = 50;

}

void Shared_ISR() {

  if (ACTIVE_ISR == ADC_ISR) {
    // Use ctx_adc
  }

}

This allows customization of the context data provided to the ISR.

Prioritizing Interrupts

Using the NVIC priority registers, we can configure unique priorities for each interrupt routed to the shared ISR. This ensures time-sensitive interrupts get handled before lower priority sources.

Some key points when prioritizing shared interrupts on Cortex M0+:

  • Set priority levels during NVIC configuration before enabling interrupts.
  • Higher priority values correspond to lower logical priorities.
  • Must clear pending bits for same or lower priorities during each interrupt.
  • To preserve priorities, use nested interrupts sparingly.

Properly prioritizing interrupts is key to avoiding missed events or timing issues when sharing an ISR between multiple sources.

Interrupt Latency Considerations

Sharing an interrupt handler can add additional latency for lower priority interrupts. For time sensitive applications, ensure the worst case latency introduced by other sources is acceptable.

Some ways to help manage interrupt latency when sharing an ISR:

  • Minimize runtime of high priority interrupt handling routines.
  • Drop lower priority events if their latency window expires.
  • Use interrupt locking or masking to create critical sections if needed.
  • Adjust NVIC priorities to match application requirements.

Testing and measurement is important to verify real-time deadlines are met when using shared ISRs.

Usage Examples

Here are some examples of how sharing an ISR can be useful on the Cortex M0+:

UART + Timer Interrupts

A UART and periodic timer interrupt can trigger the same handler. The UART is set to low priority while the timer gets high priority using the NVIC. This services time critical events quickly while handling UART in the background.

Multiple ADC Channel Conversion

The ADC peripheral may have multiple channel interrupts. Sharing an ISR allows consolidating the handling logic for each channel into one place.

Motor Control ISR

For a multi-phase motor driver, a shared ISR can handle interrupts from multiple comparators or timers to update the motor drive signals.

Conclusion

Sharing an interrupt service routine between multiple sources can optimize code size on the Cortex M0+. With careful NVIC configuration and priority management, it provides flexibility to handle several peripheral interrupts from a single handler.

Key considerations include properly identifying interrupt sources, managing context and data transfer, prioritizing timing critical events, and verifying worst-case interrupt latency. Used judiciously, shared ISRs enable efficient utilization of the Cortex M0+ interrupt capabilities.

Newsletter Form (#3)

More ARM insights right in your inbox

 


Share This Article
Facebook Twitter Email Copy Link Print
Previous Article RTL simulation for designStart Cortex-M0, M3 and M4
Next Article Force get access to Cortex-M0 if SWDIO is disabled on startup Cortex M0
Leave a comment Leave a comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

2k Followers Like
3k Followers Follow
10.1k Followers Pin
- Sponsored-
Ad image

You Might Also Like

Using ST-Link debugger with Cortex-M1 FPGA design

The ST-Link debugger is an extremely useful tool for debugging…

6 Min Read

Tips on Cortex-M3 Memory Mapping

The Cortex-M3 processor has a flexible memory mapping scheme that…

8 Min Read

What is the exception handling of the ARM Cortex-M0?

The ARM Cortex-M0 is an ultra low power 32-bit RISC…

13 Min Read

What is FPU in Cortex-M4?

The FPU (Floating Point Unit) in Cortex-M4 is a hardware…

6 Min Read
SoCSoC
  • Looking for Something?
  • Privacy Policy
  • About Us
  • Sitemap
  • Contact Us
Welcome Back!

Sign in to your account