03. FreeRTOS的中断管理

一、什么是中断

  让 CPU 打断正常运行的程序,转而去处理紧急的事件(程序),就叫 中断 。中断执行机制,可以简单概括为三步:

  1. 中断请求 ,外设产生中断请求,例如 GPIO 外部中断、定时器中断。
  2. 响应中断 ,CPU 停止执行当前程序,转而去处理中断处理程序(ISR)。
  3. 退出中断 ,执行完毕,返回被打断的程序处,继续往下执行。

  ARM Cortex-M 使用 NVIC 对不同优先级的中断进行管理,首先看一下 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;

  在 NVIC 的相关结构体中,成员变量 IP 用于配置外部中断的优先级,成员变量 IP 的定义如下所示:

__IOM uint8_t IP[240U];             // 中断优先级寄存器

  可以看到成员变量 IP 是一个 uint8_t 类型的数组,数组一共有 240 个元素,数组中每一个 8bit 的元素就用来配置对应的外部中断的优先级。

  综上可知,ARM Cortex-M 使用了 8 位宽的寄存器来配置中断的优先等级,这个寄存器就是中断优先级配置寄存器,因此最大中断的优先级配置范围位 0 ~ 255。但是 STM32 只用了中断优先级配置寄存器的高 4 位 [7:4],所以 STM32 提供最大 16 级的中断优先级。STM32 的优先级可以分为 抢占优先级子优先级

  • 抢占优先级:抢占优先级高的中断可以打断正在执行但抢占优先级低的中断。
  • 子优先级:当同时发生具有相同抢占优先级的中断时,子优先级 数值小的优先执行,但不能互相打断。

  STM32 中每个中断的优先级就由抢占优先级和子优先级共同组成,使用中断优先级配置寄存器的高 4 位来配置抢占优先级和子优先级,抢占优先级和子优先级一共有 5 种分配方式,对应这中断优先级分组的 5 个组。

优先级分组

为了方便 FreeRTOS 管理,建议将所有优先级位指定为抢占优先级位,即将优先级分组设置为组 4。函数 HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)即可完成设置,HAL_Init() 中会调用该函数。FreeRTOS 的中断管理范围,通过宏 configLIBRARY_LOWEST_INTERRUPT_PRIORITYconfigLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY(该宏在 FreeRTOSConfig.h 头文件中定义) 设置。在中断服务函数中,如果调用到 FreeRTOS 的 API 函数,那么该中断优先级必须在 FreeRTOS 所管理的范围内。

FreeRTOS管理的中断优先级

STM32 的中断优先级数值越小,中断优先级越高。FreeRTOS 的任务优先级的数值越大,任务优先级越高。

二、三个中断优先级配置寄存器

  除了外部中断,系统中断有独立的中断优先级配置寄存器,分别为 SHPR1、SHPR2、SHPR3。

  SHPR1 寄存器的地址为 0xE000ED18,用于配置 MemManage、BusFault、UsageFault 的中断优先级。

SHPR1寄存器

  SHPR2 寄存器的地址为 0xE000ED1C,用于配置 SVCall 的中断优先级。

SHPR2寄存器

  SHPR3 寄存器的地址为 0xE000ED20,用于配置 PendSV、SysTick 的中断优先级。

SHPR3寄存器

PendSV 中断的优先级设置为最低 15,以保证系统任务切换不会阻塞系统其他中断的响应!

SysTick 中断的优先级设置为 FreeeRTOS 所管理的最高优先级,以保证系统时钟节拍的精度!

三、三个中断屏蔽寄存器

  ARM Cortex-M 有三个用于屏蔽中断的寄存器,分别为 PRIMASK、FAULTMASK 和 BASEPRI。

  PRIMASK 寄存器有 32bit,但只有 bit0 有效,是可读可写的,将 PRIMASK 寄存器设置为 1 用于屏蔽除 NMI 和 HardFault 外的所有异常和中断,将 PRIMASK 寄存器清 0 用于使能中断。该寄存器的默认值就是 0,表示没有关中断。

  FAULTMASK 寄存器有 32bit,但只有 bit0 有效,也是可读可写的,将 FAULTMASK 寄存器设置为 1 用于屏蔽除 NMI 外的所有异常和中断,将 FAULTMASK 寄存器清零用于使能中断。该寄存器的默认值也是 0,表示没有关异常。

  BASEPRI 有 32bit,但只有低 8 位 [7:0] 有效,也是可读可写的。BASEPRI 用于设置一个中断屏蔽的阈值,设置好 BASEPRI 后,中断优先级低于 BASEPRI 的中断就都会被屏蔽掉,µCOS-Ⅲ 就是使用 BASEPRI 寄存器来管理受 µCOS-Ⅲ 管理的中断的,而不受 µCOS-Ⅲ 管理的中断,则不受 µCOS-Ⅲ 的影响。该寄存器的默认值也是 0,表示不关闭任何中断。

比如: BASEPRI 设置为 0x40,代表中断优先级在 4 ~ 15 内的均被屏蔽,0 ~ 3 的中断优先级正常执行。

当BASEPRI设置为0x40时中断管理示意图

四、FreeRTOS中断配置项

  FreeRTOSConfig.h 文件中有 6 个与中断相关的 FreeRTOS 配置项。

/* 中断嵌套行为配置 */
#ifdef __NVIC_PRIO_BITS
    #define configPRIO_BITS __NVIC_PRIO_BITS
#else
    #define configPRIO_BITS 4
#endif

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

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY         15                      // 中断最低优先级 

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

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    5                       // FreeRTOS可管理的最高中断优先级 

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

#define configKERNEL_INTERRUPT_PRIORITY                 (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))

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

#define configMAX_SYSCALL_INTERRUPT_PRIORITY            (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))

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

#define configMAX_API_CALL_INTERRUPT_PRIORITY           configMAX_SYSCALL_INTERRUPT_PRIORITY

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

  FreeRTOS 使用 SHPR3 寄存器配置 PendSV 和 SysTick 的中断优先级,在 FreeRTOS 的源码文件 port.c 文件中定义如下

define portNVIC_SHPR3_REG (*((volatile uint32_t *) 0xe000ed20))


#define portMIN_INTERRUPT_PRIORITY            (255UL)
#define portNVIC_PENDSV_PRI                   (((uint32_t) portMIN_INTERRUPT_PRIORITY) << 16UL)
#define portNVIC_SYSTICK_PRI                  (((uint32_t) portMIN_INTERRUPT_PRIORITY) << 24UL)

  可以看到宏 portNVIC_SHPR3_REG 被定义成了一个指向 0xE000ED20 地址的指针,而 0xE000ED20 就是 SHPR3 寄存器地址的指针,因此只需通过宏 portNVIC_SHPR3_REG 就能够访问 SHPR3 寄存器了。

  接着是宏 portNVIC_PENDSV_PRI 和宏 portNVIC_SYSTICK_PRI 分别定义成了宏 configKERNEL_INTERRUPT_PRIORITY 左移 16 位和 24 位,其中宏 configKERNEL_INTERRUPT_PRIORITY 在 FreeRTOSConfig.h 文件中被定义成了系统的最低优先等级,而左移的 16 位和 24 位,正好是 PendSV 和 SysTick 中断优先级配置在 SHPR3 寄存器中的位置,因此只需将宏 portNVIC_PENDSV_PRI 和宏 portNVIC_SYSTICK_PRI 对应地写入 SHPR3 寄存器,就能将 PendSV 和 SysTick 的中断优先级设置为最低优先级。

  接着 FreeRTOS 在启动任务调度器的函数中设置了 PendSV 和 SysTick 的中断优先级,代码如下所示:

BaseType_t xPortStartScheduler(void) 
{
    // 忽略其他代码
 
    // 设置 PendSV 和 SysTick 的中断优先级为最低中断优先级
    portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
 
    // 忽略其他代码
}

  FreeRTOS 使用 BASEPRI 寄存器来管理受 FreeRTOS 管理的中断,而不受 FreeRTOS 管理的中断不受 FreeRTOS 开关中断的影响。FreeRTOS 开关中断的宏定义,代码如下所示:

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

#define taskDISABLE_INTERRUPTS()    portDISABLE_INTERRUPTS()
#define taskENABLE_INTERRUPTS()     portENABLE_INTERRUPTS()

portFORCE_INLINE static void vPortRaiseBASEPRI( void )
{
    uint32_t ulNewBASEPRI;

    __asm volatile
    (
        "   mov %0, %1                                              \n" \
        "   msr basepri, %0                                         \n" \
        "   isb                                                     \n" \
        "   dsb                                                     \n" \
        : "=r" ( ulNewBASEPRI ) : "i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) : "memory"
    );
}

portFORCE_INLINE static void vPortSetBASEPRI( uint32_t ulNewMaskValue )
{
    __asm volatile
    (
        "   msr basepri, %0 " ::"r" ( ulNewMaskValue ) : "memory"
    );
}

  函数 vPortRaiseBASEPRI() 就是将 BASEPRI 寄存器设置为宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 配置的值。DSB 和 ISB 指令分别为数据同步隔离和指令同步隔离。函数 vPortSetBASEPRI() 就是将 BASEPRI 寄存器设置为指定的值。

  从 portDISABLE_INTERRUPTS() 宏可以看出,FreeRTOS 关闭中断的操作就是将 BASEPRI 寄存器设置为宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的值,以此来达到屏蔽受 FreeRTOS 管理的中断,而不影响到哪些不受 FreeRTOS 管理的中断。从 portENABLE_INTERRUPTS() 宏可以看出,FreeRTOS 开启中断的操作就是将 BASEPRI 寄存器的值清零,以此来取消屏蔽中断。

五、临界段代码保护

  临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段。FreeRTOS 在进出临界区的时候,通过关闭和打开受 FreeRTOS 管理的中断,以保护临界区中的代码。

  对于进出临界区, FreeRTOS 的源码中有四个相关的宏定义,分别为 taskENTER_CRITICAL()taskENTER_CRITICAL_FROM_ISR()taskEXIT_CRITICAL()taskEXIT_CRITICAL_FROM_ISR(x),这四个宏定义分别用于在中断和非中断中进出临界区,定义代码如下所示:

/* 进入临界区 */
#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) 
{

    portDISABLE_INTERRUPTS();                                                   // 关闭受 FreeRTOS 管理的中断
    uxCriticalNesting++;                                                        // 临界区支持嵌套
 
    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
    {
        mrs ulReturn, basepri                                                   // 读取 BASEPRI 寄存器 
        msr basepri, ulNewBASEPRI                                               // 设置 BASEPRI 寄存器
        dsb
        isb
    }
  
    return ulReturn; 
}

  ulPortRaiseBASEPRI() 同样是将 BASEPRI 寄存器设置为宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的值,以达到关闭中断的效果,不受 FreeRTOS 管理的中断是不受影响的。只不过函数 ulPortRaiseBASEPRI() 在设置 BASEPRI 寄存器之前,先读取了 BASEPRI 的值,并在函数的最后返回这个值,这是为了在后续从中断中退出临界区时,恢复 BASEPRI 寄存器的值。

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

void vPortExitCritical(void)
{
    configASSERT(uxCriticalNesting);                                            // 必须是进入过临界区才能退出
    uxCriticalNesting--;
  
    if( uxCriticalNesting == 0 )
    {
        portENABLE_INTERRUPTS();                                                // 打开中断
    } 
}

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

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

五、实验例程

5.1、定时器的定时功能

  定时器定时功能初始化函数:

TIM_HandleTypeDef g_tim6_handle;
TIM_HandleTypeDef g_tim7_handle;

/**
 * @brief 定时器定时功能初始化函数
 * 
 * @param htim 定时器句柄
 * @param TIMx 定时器寄存器基地址,可选值: TIMx, x可选范围: 1 ~ 14
 * @param prescaler 预分频系数,可选值: 0 ~ 65535
 * @param period 自动重装载值,可选值: 0 ~ 65535
 * 
 * @note 默认为向上计数模式
 */
void TIM_Base_Init(TIM_HandleTypeDef *htim, TIM_TypeDef *TIMx, uint16_t prescaler, uint16_t period)
{
    htim->Instance = TIMx;                                                      // 定时器寄存器基地址
    htim->Init.CounterMode = TIM_COUNTERMODE_UP;                                // 计数模式
    htim->Init.Prescaler = prescaler;                                           // 预分频系数
    htim->Init.Period = period;                                                 // 自动重装载值
    HAL_TIM_Base_Init(htim);
}

  基本定时器底层初始化函数:

/**
 * @brief 基本定时器底层初始化函数
 * 
 * @param htim 定时器句柄
 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM6)
    {
        __HAL_RCC_TIM6_CLK_ENABLE();                                            // 使能定时器6的时钟

        HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);                                      // 使能定时器6中断
        HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 3, 0);                              // 设置中断优先级
    }
    else if (htim->Instance == TIM7)
    {
        __HAL_RCC_TIM7_CLK_ENABLE();                                            // 使能定时器7的时钟
        HAL_NVIC_EnableIRQ(TIM7_IRQn);                                          // 使能定时器7中断
        HAL_NVIC_SetPriority(TIM7_IRQn, 6, 0);                                  // 设置中断优先级
    }
}

  定时器 6 中断服务函数:

/**
 * @brief 定时器6中断服务函数
 * 
 */
void TIM6_DAC_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_tim6_handle);                                         // 调用HAL库公共处理函数
}

  定时器 7 中断服务函数:

/**
 * @brief 定时器7中断服务函数
 * 
 */
void TIM7_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_tim7_handle);
}

  定时器更新中断回调函数:

/**
 * @brief 定时器更新中断回调函数
 * 
 * @param htim 定时器句柄
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM6)
    {
        HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9);
    }
    else if (htim->Instance == TIM7)
    {
        HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);
    }
}

5.2、FreeRTOS的任务函数

  main() 函数:

int main(void)
{
    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    LED_Init();
    Key_Init();

    TIM_Base_Init(&g_tim6_handle, TIM6, 8399, 9999);
    TIM_Base_Init(&g_tim7_handle, TIM7, 8399, 9999);

    __HAL_TIM_CLEAR_IT(&g_tim6_handle, TIM_IT_UPDATE);                          // 清除更新中断标志位
    HAL_TIM_Base_Start_IT(&g_tim6_handle);

    __HAL_TIM_CLEAR_IT(&g_tim7_handle, TIM_IT_UPDATE);                          // 清除更新中断标志位
    HAL_TIM_Base_Start_IT(&g_tim7_handle);

    freertos_demo();
  
    return 0;
}

  FreeRTOS 例程入口函数:

/**
 * @brief FreeRTOS的入口函数
 * 
 */
void freertos_demo(void)
{
    xTaskCreate((TaskFunction_t        ) start_task,                            // 任务函数
                (char *                ) "start_task",                          // 任务名
                (configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,                 // 任务栈大小
                (void *                ) NULL,                                  // 入口参数
                (UBaseType_t           ) START_TASK_PRIORITY,                   // 任务优先级
                (TaskHandle_t *        ) start_task_handle);                    // 任务句柄

    vTaskStartScheduler();                                                      // 开启任务调度器
}

  START_TASK 任务配置:

/**
 * START_TASK 任务配置
 * 包括: 任务优先级 任务栈大小 任务句柄 任务函数
 */
#define START_TASK_PRIORITY     1
#define START_TASK_STACK_SIZE   128

TaskHandle_t start_task_handle;

void start_task(void *pvParameters );

/**
 * @brief 开始任务的任务函数
 * 
 * @param pvParameters 任务函数的入口参数
 */
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();                                                       // 进入临界区,关闭中断

    xTaskCreate((TaskFunction_t        ) task1,                                 // 任务函数
                (char *                ) "task1",                               // 任务名
                (configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,                      // 任务栈大小
                (void *                ) NULL,                                  // 入口参数
                (UBaseType_t           ) TASK1_PRIORITY,                        // 任务优先级
                (TaskHandle_t *        ) &task1_handle);                        // 任务句柄

    vTaskDelete(NULL);                                                          // 删除任务自身

    taskEXIT_CRITICAL();                                                        // 退出临界区,重新开启中断
}

  TASK1 任务配置:

/**
 * TASK1 任务配置
 * 包括: 任务优先级 任务栈大小 任务句柄 任务函数
 */
#define TASK1_STACK_SIZE   128
#define TASK1_PRIORITY     2

TaskHandle_t task1_handle;

void task1(void *pvParameters);

/**
 * @brief 任务1的任务函数
 * 
 * @param pvParameters 任务函数的入口参数
 */
void task1(void *pvParameters)
{
    while (1)
    {
        switch (Key_Scan(0))
        {
        case KEY1_PRESS:
            portDISABLE_INTERRUPTS();                                           // 关闭中断
            Delay_ms(5000);                                                     // 延迟5s,不进行任务切换
            portENABLE_INTERRUPTS();                                            // 开启中断
            break;
        }
  
        vTaskDelay(1000);
    }
}

进入临界区,以及退出临界区,就是操作 BASEPRI 寄存器,临界区是直接屏蔽了中断,系统任务调用和 ISR 都是靠中断实现的。

这里推荐使用 Delay_ms() 函数延迟,这是因为 FreeRTOS 提供的 vTaskDelay() 函数会操作临界区,在退出临界区的时候,会开启中断。

posted @ 2024-03-03 18:24  星光樱梦  阅读(67)  评论(0编辑  收藏  举报