This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Dynamic change of Interrupt priority (sd_nvic_SetPriority)

Hi everyone,

Device : nRF52832

SDK version : 14.2

Softdevice : S132 v5.0

I am facing a problem when changing the interrupt priority dynamically. Let me explain the context of my application. My BLE module is basically a bridge between BLE and UART. I am a slave for the BLE and master for the UART.

I would like to have UART priority set to 2 when I am waiting for an answer from the nested device over UART, and UART priority set to 7 when I am not waiting for an answer (since I am the master for that communication).

The code was already developped for another application using a cortex-M4 from NXP and works perfectly fine.

Below you can see the intruction used for that:

void RS485ManageIRQ(){       
    //Timeout when finished receiving bytes
    //Currently the priority is 2
    //Now set the priority to 7 for the next interrupt
            
     __disable_irq();											    // Avoid irq restart at priority change
    NVIC_SetPriority(pRS485Vars->UART_IRQn, RS485_INTP_NEWPACK);	// Set priority under real time
    __enable_irq();
    
    
}

Now I am facing a problem since Nordic does not recommend (forbid?) to disable IRQ using __disable_irq() since it blocks the time-critical operation of the softdevice.

But in all documentation I found, it is strongly recommended to disable all IRQ before using the function NVIC_SetPriority.

Actually now my code looks like this :

void RS485ManageIRQ(){       
    //Timeout when finished receiving bytes
    //Currently the priority is 2
    //Now set the priority to 7 for the next interrupt
            
    //__disable_irq();	//cannot disable all IRQ
    sd_nvic_SetPriority(nrf_drv_get_IRQn((void *)pRS485Vars->Reg.pUart), RS485_INTP_CALLBACK);	// Set priority under real time
    //__enable_irq();
    
    
}

I am actually using the sd_ wrapper function for the NVIC_SetPriority, since no SVC calls are made inside it I assume that this should not be a problem calling it from interrupt level 2.

Using sd_nvic_critical_region_enter/exit function instead of __disable_irq() is not sufficient if I want to follow the recommendation since it disable only the application interruption.

Do you recommend a workaround to dynamically change the interrupt priority?

This is my first message on the DevZone, I hope that I explained my issue well enough.

Best regards,

Parents
  • Dear haakonsh,

    Thanks for your reply.

    My goal is for sure not to lose any data on the UART and not to have to lower the baudrate because of interruption @level 4 "SoftDevice API calls and non-time-critical processing".

    The reason why I am not using the DMA right now is for the sake of not changing completely the strategy we established when developping the application on the cortex-M4 from NXP. For debugging reason and stability, we would like to keep the codes we developped with different devices to be as close as possible.

    So before trying to change the strategy, I wanted to know if the interrupt priority could be changed dynamically in a safely manner compliant with the SoftDevice.

    Can the sd_nvic_SetPriority() be called safely from interrupt priority 2-3-5-6-7 or it should not be used at all? how can I disable interrupts before calling that function?

    You talked about not spending toot much time at priority 2, can you give me an idea of the maximum time that should be spent @priority 2? I understand that this is a difficult question.

    Thank you for the time you spent answering my questions.

    Best regards,

    Kevin

  • My previous statement, "The reason I ask is that the SoftDevice will crash if the application spends too much time at priority 2.", is erroneous.

    It will not crash, but there are some procedures for which the application should respond to. (eg phy update). If the application spends too much time in 2 and 3 (more than 30 seconds), without responding to the PHY Update Request event, then it will result in disconnection of BLE link

    I believe you can safely use sd_nvic_SetPriority from priority 2 with sd_nvic_critical_region_enter/exit. 




  • Hi haakonsh,

    Thanks for your answer, I will make some test on my side and I will come back to you when I get the results.

    Best regards,

    Kevin

  • Dear Haakonsh,

    I made some intense testing on my side. The only solution working reliably is using the original NVIC_SetPriority(...) wrapped around __disable_irq() and __enable_irq().

    	__disable_irq();
    	NVIC_SetPriority(pRS485Vars->UART_IRQn, RS485_INTP_NEWPACK);	// Set priority under real time
    	__enable_irq();

    As stated before, in all the documentation I found  (e.g. The definitive guide to ARM Cortex-M3/M4 processors by Joseph Yiu) for NVIC_SetPriority() function state that you must disable all interrupts before calling that function. This was definitely the problem using the sd_nvic_critical_region_enter/exit() functions, this was not disabling all the interupts (only the application ones) therefore leading to unexpected program behavior. 

    I read on the DevZone that __disable_irq() and __enable_irq() functions should not be used, even for short period of time. But when I looked closer at the implementation of the function sd_nvic_critical_region_enter() I found that __disable_irq() and __enable_irq() are used inside.

    __STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t * p_is_nested_critical_region)
    {
      int was_masked = __sd_nvic_irq_disable();
      if (!nrf_nvic_state.__cr_flag)
      {
        nrf_nvic_state.__cr_flag = 1;
        nrf_nvic_state.__irq_masks[0] = ( NVIC->ICER[0] & __NRF_NVIC_APP_IRQS_0 );
        NVIC->ICER[0] = __NRF_NVIC_APP_IRQS_0;
        nrf_nvic_state.__irq_masks[1] = ( NVIC->ICER[1] & __NRF_NVIC_APP_IRQS_1 );
        NVIC->ICER[1] = __NRF_NVIC_APP_IRQS_1;
        *p_is_nested_critical_region = 0;
      }
      else
      {
        *p_is_nested_critical_region = 1;
      }
      if (!was_masked)
      {
        __sd_nvic_irq_enable();
      }
      return NRF_SUCCESS;
    }

    The call to those functions are made inside __sd_nvic_irq_disable() and __sd_nvic_irq_enable().

    __STATIC_INLINE int __sd_nvic_irq_disable(void)
    {
      int pm = __get_PRIMASK();
      __disable_irq();
      return pm;
    }
    
    __STATIC_INLINE void __sd_nvic_irq_enable(void)
    {
      __enable_irq();
    }

    Therefore I assume that using __disable_irq() and __enable_irq() function are allowed for criticals part of the code (of course during the smallest possible time), which is my case at several points.

    I would be happy to get your thoughts on this subject.

    Thank you for the time you spent on that topic.

    Best regards,

    Kevin

Reply
  • Dear Haakonsh,

    I made some intense testing on my side. The only solution working reliably is using the original NVIC_SetPriority(...) wrapped around __disable_irq() and __enable_irq().

    	__disable_irq();
    	NVIC_SetPriority(pRS485Vars->UART_IRQn, RS485_INTP_NEWPACK);	// Set priority under real time
    	__enable_irq();

    As stated before, in all the documentation I found  (e.g. The definitive guide to ARM Cortex-M3/M4 processors by Joseph Yiu) for NVIC_SetPriority() function state that you must disable all interrupts before calling that function. This was definitely the problem using the sd_nvic_critical_region_enter/exit() functions, this was not disabling all the interupts (only the application ones) therefore leading to unexpected program behavior. 

    I read on the DevZone that __disable_irq() and __enable_irq() functions should not be used, even for short period of time. But when I looked closer at the implementation of the function sd_nvic_critical_region_enter() I found that __disable_irq() and __enable_irq() are used inside.

    __STATIC_INLINE uint32_t sd_nvic_critical_region_enter(uint8_t * p_is_nested_critical_region)
    {
      int was_masked = __sd_nvic_irq_disable();
      if (!nrf_nvic_state.__cr_flag)
      {
        nrf_nvic_state.__cr_flag = 1;
        nrf_nvic_state.__irq_masks[0] = ( NVIC->ICER[0] & __NRF_NVIC_APP_IRQS_0 );
        NVIC->ICER[0] = __NRF_NVIC_APP_IRQS_0;
        nrf_nvic_state.__irq_masks[1] = ( NVIC->ICER[1] & __NRF_NVIC_APP_IRQS_1 );
        NVIC->ICER[1] = __NRF_NVIC_APP_IRQS_1;
        *p_is_nested_critical_region = 0;
      }
      else
      {
        *p_is_nested_critical_region = 1;
      }
      if (!was_masked)
      {
        __sd_nvic_irq_enable();
      }
      return NRF_SUCCESS;
    }

    The call to those functions are made inside __sd_nvic_irq_disable() and __sd_nvic_irq_enable().

    __STATIC_INLINE int __sd_nvic_irq_disable(void)
    {
      int pm = __get_PRIMASK();
      __disable_irq();
      return pm;
    }
    
    __STATIC_INLINE void __sd_nvic_irq_enable(void)
    {
      __enable_irq();
    }

    Therefore I assume that using __disable_irq() and __enable_irq() function are allowed for criticals part of the code (of course during the smallest possible time), which is my case at several points.

    I would be happy to get your thoughts on this subject.

    Thank you for the time you spent on that topic.

    Best regards,

    Kevin

Children
  • Hey Kevin,

    To say that you absolutely cannot use __disable_irq() is probably erroneous, but I will still not recommend using it. This is because we can only test on our own codebase and the improper use of __disable_irq() is guaranteed to crash the SoftDevice.

    That being said, I believe you can safely use __disable_irq() given that you extensively test the stability of your application, and that is most likely what you've been doing already. 

    The risk of a crash is a function of how often you disable interrupts, for how long, and how often the SoftDevice will need to execute timing-critical interrupts. 

    I think you should also assume that you WILL crash "once in a blue moon" and handle that scenario, though that is true for all SW.  

    Cheers,

    Håkon.

Related