中断管理笔记
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 寄存器。
Comments NOTHING