中断管理

发布于 2023-08-11  1647 次阅读


内容纲要

中断管理笔记

ARM Cortex-M 中断

中断优先级管理

NVIC 在 CMSIS 中 的结构体定义如下

typedef struct
{
     __IOM uint32_t ISER[8U]; /* 中断使能寄存器 */
     uint32_t RESERVED0[24U];
     __IOM uint32_t ICER[8U]; /* 中断除能寄存器 */
     uint32_t RSERVED1[24U];
     __IOM uint32_t ISPR[8U]; /* 中断使能挂起寄存器 */
     uint32_t RESERVED2[24U];
     __IOM uint32_t ICPR[8U]; /* 中断除能挂起寄存器 */
     uint32_t RESERVED3[24U];
     __IOM uint32_t IABR[8U]; /* 中断有效位寄存器 */
     uint32_t RESERVED4[56U];
     __IOM uint8_t IP[240U]; /* 中断优先级寄存器 */
     uint32_t RESERVED5[644U];
     __OM uint32_t STIR; /* 软件触发中断寄存器 */
} NVIC_Type;

优先级分组

中断控制状态寄存器

FreeRTOS中断配置项

1. configPRIO_BITS

​ 此宏是用于辅助配置的宏,主要用于辅助配置宏 configKERNEL_INTERRUPT_PRIORITY 和宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的,此宏应定义为 MCU 的 8 位优先级配 置寄存器实际使用的位数,因为 STM32 只使用到了中断优先级配置寄存器的高 4 位,因此,此宏应配置为4。

2. configLIBRARY_LOWEST_INTERRUPT_PRIORITY

​ 此宏是用于辅助配置宏 configKERNEL_INTERRUPT_PRIORITY 的,此宏应设置为 MCU 的最低优先等级,因为 STM32 只使用了中断优先级配置寄存器的高 4 位,因此 MCU 的最低优 先等级就是 2^4-1=15,因此,此宏应配置为 15。

3. configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY

​ 此宏是用于辅助配置宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的,此宏适用于配 置 FreeRTOS 可管理的最高优先级的中断,此功能就是操作 BASEPRI 寄存器来实现的。此宏的 值可以根据用户的实际使用场景来决定,本教程的配套例程源码全部将此宏配置为 5,即中断 优先级高于 5 的中断不受 FreeRTOS 影响。

4. configKERNEL_INTERRUPT_PRIORITY

​ 此宏应配置为 MCU 的最低优先级在中断优先级配置寄存器中的值,在 FreeRTOS 的源码 中,使用此宏将 SysTick 和 PenSV 的中断优先级设置为最低优先级。因为 STM32 只使用了中 断优先级配置寄存器的高 4 位,因此,此宏应配置为最低中断优先级在中断优先级配置寄存器 高 4 位的表示,即(configLIBRARY_LOWEST_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))。

5. configMAX_SYSCALL_INTERRUPT_PRIORITY

​ 此宏用于配置 FreeRTOS 可管理的最高优先级的中断,在 FreeRTOS 的源码中,使用此宏来 打开和关闭中断。因为 STM32 只使用了中断优先级配置寄存器的高 4 位,因此,此宏应配置为 (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))。

6. configMAX_API_CALL_INTERRUPT_PRIORITY

​ 此宏为宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的新名称,只被用在 FreeRTOS 官方一些新的移植当中,此宏于宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 是等价的。

FreeRTOS 中断管理

FreeRTOS开关中断

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
#define taskDISABLE_INTERRUPTS() portDISABLE_INTERRUPTS()
#define taskENABLE_INTERRUPTS() portENABLE_INTERRUPTS()

1. 函数 vPortRaiseBASEPRI()

{
     uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

     __asm
     {
     /* 设置 BasePRI 寄存器 */
     msr basepri, ulNewBASEPRI
     dsb
     isb
     }
}

​ 可以看到,函数 vPortRaiseBASEPRI() 就是将 BASEPRI 寄 存 器 设 置 为 宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 配置的值。

2. 函数 vPortSetBASEPRI()

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
     __asm
     {
     /* 设置 BasePRI 寄存器 */
     msr basepri, ulBASEPRI
     }
}

​ 可以看到,函数 vPortSetBASEPRI()就是将 BASEPRI 寄存器设置为指定的值。

3.FreeRTOS开关中断的宏定义

宏 portDISABLE_INTERRUPTS()
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()

​ 从上面的宏定义可以看出,FreeRTOS 关闭中断的操作就是将 BASEPRI 寄存器设置为宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的值,以此来达到屏蔽受 FreeRTOS 管理的中 断,而不影响到哪些不受 FreeRTOS 管理的中断。

宏 portENABLE_INTERRUPTS()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )

​ 从上面的宏定义可以看出,FreeRTOS 开启中断的操作就是将 BASEPRI 寄存器的值清零, 以此来取消屏蔽中断。

RTOS进出临界区

/* 进入临界区 */
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define portENTER_CRITICAL() vPortEnterCritical()
/* 中断中进入临界区 */
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
/* 退出临界区 */
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define portEXIT_CRITICAL() vPortExitCritical()
/* 中断中退出临界区 */
#define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)

宏 taskENTER_CRITICAL()

​ 此宏用于在非中断中进入临界区,此宏展开后是函数 vPortEnterCritical() ,函数 vPortEnterCritical()的代码如下所示:

void vPortEnterCritical( void )
{
     /* 关闭受 FreeRTOS 管理的中断 */
     portDISABLE_INTERRUPTS();
     /* 临界区支持嵌套 */
     uxCriticalNesting++;

     if( uxCriticalNesting == 1 )
     {
     /* 这个函数不能在中断中调用 */
     configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
     }
}

​ 从上面的代码中可以看出,函数 vPortEnterCritical()进入临界区就是关闭中断,当然了,不 受 FreeRTOS 管理的中断是不受影响的。还可以看出,FreeRTOS 的临界区是可以嵌套的,意思 就是说,在程序中可以重复地进入临界区,只要后续重复退出相同次数的临界区即可。

在上面的代码中还有一个断言,代码如下所示:

if( uxCriticalNesting == 1 )
{
     /* 这个函数不能在中断中调用 */
     configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}

​ 可以看出,宏 portNVIC_INT_CTRL_REG 就是指向中断控制状态寄存器(ICSR)的指针, 而宏 portVECTACTIVE_MASK 就是 ICSR 寄存器中 VECTACTIVE 段对应的位置,因此这个断 言就是用来判断当第一次进入临界区的时候,是否是从中断服务函数中进入的,因为函数 vportEnterCritical()是用于从非中断中进入临界区,如果用户错误地在中断服务函数中调用函数 vportEnterCritical(),那么就会通过断言报错。

宏 taskENTER_CRITICAL_FROM_ISR()

​ 此宏用于从中断中进入临界区,此宏展开后是函数 ulPortRaiseBASEPRI(),函数 ulPortRaiseBASEPRI()的代码如下所示:

static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
     uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

     __asm
     {
     /* 读取 BASEPRI 寄存器 */
     mrs ulReturn, basepri
     /* 设置 BASEPRI 寄存器 */
     msr basepri, ulNewBASEPRI
     dsb
     isb
     }

     return ulReturn;
}

宏 taskEXIT_CRITICAL()

​ 此 宏 用 于 从 非 中 断 中 退 出 临 界 区 , 此 宏 展 开 后 是 函 数 vPortExitCritical() ,函数 vPortExitCritical()的代码如下所示:

void vPortExitCritical( void )
     {
         /* 必须是进入过临界区才能退出 */
         configASSERT( uxCriticalNesting );
         uxCriticalNesting--;

     if( uxCriticalNesting == 0 )
     {
         /* 打开中断 */
         portENABLE_INTERRUPTS();
     }
}

​ 这个函数就很好理解了,就是将用于临界区嵌套的计数器减 1,当计数器减到 0 的时候, 说明临界区已经没有嵌套了,于是调用函数 portENABLE_INTERRUPT()打开中断。在函数的一 开始还有一个断言,这个断言用于判断用于临界区嵌套的计数器在进入此函数的不为 0,这样 就保证了用户不会在还未进入临界区时,就错误地调用此函数退出临界区。

taskEXIT_CRITICAL_FROM_ISR(x)

​ 此宏用于从中断中退出临界区,此宏展开后是调用了函数 vPortSetBASEPRI(),并将参数 x 传入函数 vPortSetBASEPRI()。其中参数 x 就是宏 taskENTER_CRITICAL_FROM_ISR()的返回 值,用于在从中断中对出临界区时,恢复 BASEPRI 寄存器。

世界のネズミは彼らが望むものに依存し、彼らは彼ら自身から誰も求めません
最后更新于 2023-08-27