Electronic – How to interrupt an ISR with a higher-priority ISR on an STM32F105

interruptsmicrocontrollerstm32stm32f10x

I have two external interrupt sources coming into an STM32F105. I want one of them (call it "IRQHigh") to pre-empt the other ("IRQLow"). Currently, if IRQHigh is triggered during the IRQLow ISR, the program flow waits until I clear the IRQLow ITPending bit before it branches into the IRQHigh ISR.

The STM32F105 is a Cortex-M3-based microcontroller. It supports nested interrupts. My application is written in C, using GCC (arm-none-eabi-gcc) in Eclipse, with the STM32F1 Standard Peripheral Library.

I think I have the priorities configured correctly but I must be missing something.

Here is the corresponding initialization code. I have stripped out AFB clock commands, GPIO config, etc, since each subsystem seems to work fine by itself:

#define IRQHIGH_EXTI_PORT  GPIO_PortSourceGPIOA
#define IRQHIGH_EXTI_PIN   GPIO_PinSource3
#define IRQHIGH_EXTI_LINE  EXTI_Line3
#define IRQHIGH_EXTI_IRQn  EXTI3_IRQn

#define IRQLOW_EXTI_PORT   GPIO_PortSourceGPIOC
#define IRQLOW_EXTI_PIN    GPIO_PinSource11
#define IRQLOW_EXTI_LINE   EXTI_Line11
#define IRQLOW_EXTI_IRQn   EXTI15_10_IRQn

NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;

// Sixteen levels of pre-emption priority, no subpriorities
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

// IRQHigh

    // Connect EXTI Line to GPIO Pin
    GPIO_EXTILineConfig(IRQHIGH_EXTI_PORT, IRQHIGH_EXTI_PIN);

    // Configure EXTI line
    EXTI_InitStructure.EXTI_Line = IRQHIGH_EXTI_LINE;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    // Configure and enable EXTI Interrupt
    NVIC_InitStructure.NVIC_IRQChannel = IRQHIGH_EXTI_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

// IRQLow

    // Connect EXTI Line to GPIO Pin
    GPIO_EXTILineConfig(IRQLOW_EXTI_PORT, IRQLOW_EXTI_PIN);

    // Configure EXTI line
    EXTI_InitStructure.EXTI_Line = IRQLOW_EXTI_LINE;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    // Configure, but do not enable, EXTI Interrupt
    NVIC_InitStructure.NVIC_IRQChannel = IRQLOW_EXTI_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;
    NVIC_Init(&NVIC_InitStructure);

The IRQ handlers are set up as so:

void EXTI3_IRQHandler(void)
{
    IRQHigh();
    EXTI_ClearITPendingBit(IRQHIGH_EXTI_LINE);
}

void EXTI15_10_IRQHandler(void)
{
    if (EXTI_GetITStatus(IRQLOW_EXTI_LINE) == SET)
    {
        if (IRQLow()) // This returns a non-zero value if an overflow happened
        {
            CleanUpOverflow();
        }

        // Clear interrupt bit.
        EXTI_ClearITPendingBit(IRQLOW_EXTI_LINE);
    }
    else // unknown EXTI source
    {
        ErrorHandler(ERR_UNKNOWN_EXTI15_10_IRQ); // This never happens
    }
}

A few things of note:

  1. IRQHigh() and IRQLow() each take significant time to complete (which is why I want one to interrupt the other)

  2. IRQLow is not initially enabled, but is enabled down the line with NVIC_EnableIRQ(IRQLOW_EXTI_IRQn);

  3. Within EXTI15_10_IRQHandler(), I'm getting a return value from IRQLow().

  4. I have declared the xxx_IRQHandler() functions with and without __attribute__ ((interrupt ("IRQ"))). I understand that this attribute isn't necessary with Cortex-M3, but I tried anyway (and got the same results).

What am I doing wrong?


Update: With help from a comment by @Jeroen3, I've discovered that IRQLow is interrupting IRQHigh. Now I need to find out why…

Best Answer

The priorities are not being initialized correctly.

The code in the question initializes the NVIC like so:

// IRQHigh, enabled immediately
NVIC_InitStructure.NVIC_IRQChannel = IRQHIGH_EXTI_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

// IRQLow, to be enabled later:
NVIC_InitStructure.NVIC_IRQChannel = IRQLOW_EXTI_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;
NVIC_Init(&NVIC_InitStructure);

Then, later, IRQLow is explicitly enabled with NVIC_EnableIRQ().

The problem is that the NVIC_Init() function (from the SPL) doesn't actually initialize anything (!) if IRQChannelCmd = DISABLE. It simply sets the ICER register to disable that interrupt.

This seems like a bug to me, but so be it. Now I need to go and check the other xxx_Init() functions in the SPL and see if anything else might bite me later :)

There are a few solutions. The one I prefer is to leave out all five lines of the IRQLow init sequence and replace them with this:

NVIC_SetPriority(IRQLOW_EXTI_IRQn, 5);

Then, when it is time, NVIC_EnableIRQ() can be used as intended.

Note that this works because the NVIC is configured with NVIC_PriorityGroup_4 (no subpriorities). Different PriorityGroup settings would require you'd to configure the subpriority, too.