FreeRTOS学习笔记_中断管理 

犀利的毛毛虫 发布于 2025-03-07 423 次阅读


FreeRTOS中断管理(熟悉)

在STM32中,中断优先级是通过中断优先级配置寄存器的高4位 [7:4] 来配置的。因此STM32支持最多16级中断优先级,其中数值越小表示优先级越高,即更紧急的中断。(FreeRTOS任务调度的任务优先级相反,是数值越大越优先)

FreeRTOS可以与STM32原生的中断机制结合使用,但它提供了自己的中断管理机制,主要是为了提供更强大和灵活的任务调度和管理功能。

FreeRTOS中,将PendSV和SysTick设置最低中断优先级(数值最大,15),保证系统任务切换不会阻塞系统其他中断的响应。

FreeRTOS利用BASEPRI寄存器实现中断管理,屏蔽优先级低于某一个阈值的中断。比如: BASEPRI设置为0x50(只看高四位,也就是5),代表中断优先级在5~15内的均被屏蔽,0~4的中断优先级正常执行。

在中断服务函数中调用FreeRTOS的API函数需注意:

  • 中断服务函数的优先级需在FreeRTOS所管理的范围内,阈值由configMAX_SYSCALL_INTERRUPT_PRIORITY指定。
  • 建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理。
  • 在中断服务函数里边需调用FreeRTOS的API函数,必须使用带“FromISR”后缀的函数。

FreeRTOS的开关中断

FreeRTOS 开关中断函数其实是宏定义,在 portmacro.h 中有定义,如下:

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

调用portENABLE_INTERRUPTS() 它, FreeRTOS会打开管理的所有中断

调用portDISABLE_INTERRUPTS() 它, FreeRTOS会关闭管理的所有中断

FreeRTOS的临界段代码

临界段代码,又称为临界区,指的是那些必须在不被打断的情况下完整运行的代码段。例如,某些外设的初始化可能要求严格的时序,因此在初始化过程中不允许被中断打断。在FreeRTOS中,进入临界段代码时需要关闭中断,在处理完临界段代码后再重新开启中断。FreeRTOS系统本身包含许多临界段代码,并对其进行了保护。在编写用户程序时,有些情况下也需要添加临界段代码以确保代码的完整性。

与临界段代码保护有关的函数有 4 个:

  • taskENTER_CRITICAL() :进入临界段。
  • taskEXIT_CRITICAL() :退出临界段。
  • taskENTER_CRITICAL_FROM_ISR() :进入临界段(中断级)。
  • taskEXIT_CRITICAL_FROM_ISR():退出临界段(中断级)。

进入和退出临界段是成对使用的。每进入一次临界段,全局变量uxCriticalNesting都会加一,每调用一次退出临界段,uxCriticalNesting减一,只有当 uxCriticalNesting 为 0 的时候才会调用函数 portENABLE_INTERRUPTS()使能中断。这确保了在存在多个临界段代码的情况下,不会因为某个临界段代码的退出而破坏其他临界段的保护。只有当所有的临界段代码都退出时,中断才会被重新使能。

挂起和恢复任务调度器

挂起和恢复任务调度器, 调用此函数不需要关闭中断:

  • vTaskSuspendAll():挂起任务调度器。
  • xTaskResumeAll():恢复任务调度器。

与临界区不同的是,挂起任务调度器时未关闭中断;这种方式仅仅防止了任务之间的资源争夺,中断仍然可以直接响应;挂起调度器的方法适用于临界区位于任务与任务之间的情况;这样既不需要延迟中断,同时又能确保临界区的安全性。

FreeRTOS中断管理实验(掌握)

学会FreeRTOS中断管理:

  • 设置管理的优先级范围:5~15。
  • 使用两个定时器,一个优先级为4,一个优先级为6。
  • 两个定时器每1s,打印一段字符串。
  • task1:按下KEY1,关中断,按下KEY2,开中断。

观察两个定时器的打印情况。

main.c代码清单

/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
    {
       printf("TIM2优先级为4,运行中...\r\n");
    }
    else if(htim->Instance == TIM3)
    {
       printf("TIM3优先级为6,运行中...\r\n");
    }
}
/* USER CODE END 0 */
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim2);  
  HAL_TIM_Base_Start_IT(&htim3);  
  /* USER CODE END 2 */

FreeRTOSConfig.h代码清单

/*3. 中断嵌套行为相关配置 cm3内核:我们要求4个优先级位全部为抢占优先级位
    最高优先级是 0
    最低优先级是 15
*/
/* 设置 RTOS 内核自身使用的中断优先级。 一般设置为最低优先级, 不至于屏蔽其他优先级程序*/
#define configKERNEL_INTERRUPT_PRIORITY (15 << 4)
/* 设置了 调用中断安全的 FreeRTOS API 函数的最高中断优先级。 FreeRTOS 的管理的最高优先级 */        
#define configMAX_SYSCALL_INTERRUPT_PRIORITY  (5 << 4)
/* 同上. 仅用于新版移植。 这两者是等效的。 */  
#define configMAX_API_CALL_INTERRUPT_PRIORITY   configMAX_SYSCALL_INTERRUPT_PRIORITY

freertos_demo.c代码清单

#include "freertos_demo.h"
/* freertos相关的头文件,必须的 */
#include "FreeRTOS.h"
#include "task.h"
/* 需要用到的其他头文件 */
#include "LED.h"
#include "Key.h"

/* 启动任务的配置 */
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);

/* 任务1的配置 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);

/**
 * @description: 启动FreeRTOS
 * @return {*}
 */
void freertos_start(void)
{
    /* 1.创建一个启动任务 */
    xTaskCreate((TaskFunction_t)start_task,               // 任务函数的地址
                (char *)"start_task",                     // 任务名字符串
                (configSTACK_DEPTH_TYPE)START_TASK_STACK, // 任务栈大小,默认最小128,单位4字节
                (void *)NULL,                             // 传递给任务的参数
                (UBaseType_t)START_TASK_PRIORITY,         // 任务的优先级
                (TaskHandle_t *)&start_task_handle);      // 任务句柄的地址

    /* 2.启动调度器:会自动创建空闲任务 */
    vTaskStartScheduler();
}

/**
 * @description: 启动任务:用来创建其他Task
 * @param {void} *pvParameters
 * @return {*}
 */
void start_task(void *pvParameters)
{
    /* 进入临界区:保护临界区里的代码不会被打断 */
    taskENTER_CRITICAL();

    /* 创建1个任务 */
    xTaskCreate((TaskFunction_t)task1,
                (char *)"task1",
                (configSTACK_DEPTH_TYPE)TASK1_STACK,
                (void *)NULL,
                (UBaseType_t)TASK1_PRIORITY,
                (TaskHandle_t *)&task1_handle);

    /* 启动任务只需要执行一次即可,用完就删除自己 */
    vTaskDelete(NULL);

    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/**
 * @description: 任务一:实现LED1每500ms闪烁一次
 * @param {void} *pvParameters
 * @return {*}
 */
void task1(void *pvParameters)
{

    uint8_t key = 0;
    while (1)
    {
        key = Key_Detect();
        if (key == KEY1_PRESS)
        {
            /* 关中断 */
            printf(">>>>关中断.....\r\n");
            portDISABLE_INTERRUPTS();
        }
        else if (key == KEY2_PRESS)
        {
            /* 开中断 */
            printf(">>>>开中断.....\r\n");
            portENABLE_INTERRUPTS();
        }

        /* 为了观察实验现象,不要调用freertos的延时函数,底层会去开关中断,影响现象 */
        // vTaskDelay(500);
        /* 使用HAL_Delay前提:HAL时钟修改成其他定时器,并且中断优先级高于freertos的管理范围 */
        HAL_Delay(500);
    }
}