没志青年
发布于 2025-06-16 / 35 阅读
0

FreeRTOS 中断管理

中断配置

FreeRTOS 屏蔽的是 NVIC 配置的中断优先级,而不是芯片中固定的中断号。

使用 FreeRTOS 的时候,单片机优先级组通常必须设置为 NVIC_PriorityGroup_4,以适应 FreeRTOS,因为 FreeRTOS 只关注抢占优先级。

对于 Cortex-M,数字越小,优先级越高

对于 Coretx-M,NVIC 的中断优先级寄存器是 8-bit 宽,但 __NVIC_PRIO_BITS 决定了 MCU 实际实现了几位(通常 Cortex-M0/M3/M4/M7 常见为 4,即 0~15),没实现的低位(8-__NVIC_PRIO_BITS)固定为0,硬件忽略。

#define configMAX_SYSCALL_INTERRUPT_PRIORITY (5<<(8-__NVIC_PRIO_BITS))
  • <=5:优先级高的中断,不能调用 FreeRTOS API,不会被临界区屏蔽

  • 6 ~ 15:优先级低的中断,可以调用 FreeRTOS API,会被临界区屏蔽

注意(5<<(8-__NVIC_PRIO_BITS))这种写法,此时值为0x50,而直接使用5的话,值为0x05,由于NVIC只使用高4位,就变成了0x00,那就不对了。

全局中断

关中断 vPortRaiseBASEPRI()

开关中断就是操作 ARM Cortex-M 内核中 BASEPRI 寄存器(中断控制寄存器)来屏蔽低优先级的中断。

注意:FreeRTOS 中的关中断不是把全部中断都关闭了,而是根据 configMAX_SYSCALL_INTERRUPT_PRIORITY 的值进行屏蔽,因此高优先级的硬件中断仍开启,调度器仍正常工作。

实现路径:宏定义 taskDISABLE_INTERRUPTS() -> 宏定义 portDISABLE_INTERRUPTS() -> 函数 vPortRaiseBASEPRI()

    static portFORCE_INLINE void vPortRaiseBASEPRI(void)
    {
        uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

        __asm
        {
            msr basepri, ulNewBASEPRI
            /* 屏蔽优先级数值大于等于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断 */
            dsb
            isb
        }
    }

BASEPRI 寄存器最多 9 位:

  • 0:不屏蔽任何中断,默认值。

  • 大于 0:屏蔽大于等于该值的中断(即比这个中断优先级低的中断)

不对???

上面的代码中,不难发现,比如你设置的configMAX_SYSCALL_INTERRUPT_PRIORITY为5,它把5也屏蔽了,而不是从6开始屏蔽,我也不知道为什么这么设计,问了AI,也说不清楚,估计是工业级代码的保守做法吧。

因此,在项目中,还是别用 configMAX_SYSCALL_INTERRUPT_PRIORITY 这个优先级,防止出现奇奇怪怪的问题。

或者,可以直接修改这段代码,uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY + 1;

开中断 vPortSetBASEPRI(0)

开/关中断是在 portmacro.h 文件中定义的,因为不同硬件平台的实现方式不一样。

    static portFORCE_INLINE void vPortSetBASEPRI(uint32_t ulBASEPRI)
    {
        __asm
        { 
            msr basepri, ulBASEPRI
        }
    }

临界区

临界区:防止代码被中断打断,保护共享资源不被破坏的一段代码。

在项目中,推荐使用临界区,而非直接使用开关中断的方式,防止写代码时疏忽,造成程序异常。

进入临界区 taskENTER_CRITICAL()

进入/退出临界区在 port.c 文件中实现,在 portmacro.h 文件中重命名。

#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()

实现路径:宏定义 taskENTER_CRITICAL() -> 宏定义 portENTER_CRITICAL() -> 函数 vPortEnterCritical()

临界区函数是在开/关中断函数的基础上,增加了一个临界区嵌套计数(uxCriticalNesting),目的是,当多次进入临界区时,确保只在完全退出时恢复中断。

退出临界区 taskEXIT_CRITICAL()

#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_ISR(x)

实现路径:宏定义 taskEXIT_CRITICAL() -> 宏定义 portEXIT_CRITICAL() -> 函数 vPortExitCritical()

关中断和进入临界区,都能防止任务切换(因为通常systick优先级设置为最低),以及禁用了能使用freertos isr的中断。