自学内容网 自学内容网

08:软件定时器+中断管理

1、软件定时器

   软件定时器是基于 FreeRTOS 内核提供的时间管理功能实现的,允许开发者创建、启动、停止、删除和管理定时器,从而实现在任务中对时间的灵活控制。

软件定时器硬件定时器
FreeRTOS提供的功能来模拟定时器,依赖系统的任务调度器来进行计时和任务调度由芯片或微控制器提供,独立于 CPU,可以在后台运行,不受任务调度器的影响
精度和分辨率可能受到任务调度的影响具有更高的精度和分辨率
不需要额外的硬件资源,但可能会增加系统的负载占用硬件资源,不会增加 CPU 的负载

  软件定时器服务任务是任务调度器中的一个特殊任务,专门用于管理和维护软件定时器的正常运行。如果configUSE_TIMERS 设置为1,在创建软件定时器时,会自动创建一个软件定时器的服务任务,它主要负责调用超时软件定时器的超时回调函数、处理软件定时器命令队列。既然是任务,那么就需要配置优先级,任务栈大小,和命令队列(用于传递处理软件定时器命令)

使用软件定时器需要添加如下代码

/* 软件定时器相关定义 */
#include "timers.h"//添加定时器头文件
#define configUSE_TIMERS 1                                          /* 1: 使能软件定时器, 默认: 0。使能后需指定下面3个 */
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1)        /* 定义软件定时器任务的优先级 */
#define configTIMER_QUEUE_LENGTH 5                                  /* 定义软件定时器命令队列的长度*/
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2) /* 定义软件定时器任务的栈空间大小*/

创建软件定时器

/**
 * 函数:创建软件定时器
 * pcTimerName:定时器名字
 * xTimerPeriodInTicks:定时器周期Tick
 * xAutoReload:pdTrue(周期性定时器),pdFalse(一次性)
 * pvTimerID: 定时器ID
 * pxCallbackFunction:定时器回调函数
 * 返回值:TimerHandle_t类型变量,Timer句柄
 */
xTimerCreate(const char * const pcTimerName, 
             const TickType_t xTimerPeriodInTicks,
             const BaseType_t xAutoReload,
             void * const pvTimerID,
             TimerCallbackFunction_t pxCallbackFunction) 动态方式创建软件定时器
xTimerCreateStatic() 静态方式创建软件定时器

开启定时器(启动定时器服务任务)

/**
 * 函数:启动软件定时器
 * xTimer:定时器句柄
 * xTicksToWait:等待Tick
 */
xTimerStart(xTimer, xTicksToWait) 开启软件定时器定时
xTimerStartFromISR() 在中断中开启软件定时器定时

启动定时器/停止定时器/复位定时器/更改超时时机的这些API函数其实是软件定时器命令,任务函数中调用这些API函数本质上是在给软件定时器的命令队列写入数据。而通过之前的学习就知道,写入队列需要等待Tick,因此这些对软件定时器配置API需要等待Tick。

停止定时器(将定时器任务挂起)

/**
 * 函数:停止软件定时器定时
 * xTimer:定时器句柄
 * xTicksToWait:等待Tick
 */
xTimerStop(xTimer, xTicksToWait) 停止软件定时器定时
xTimerStopFromISR() 在中断中停止软件定时器定时

复位定时器

/**
 * 函数:复位软件定时器定时
 * xTimer:定时器句柄
 * xTicksToWait:等待Tick
 */
xTimerReset() 复位软件定时器定时
xTimerResetFromISR() 在中断中复位软件定时器定时

更改超时时间

/**
 * 函数:更改软件定时器的定时超时时间
 * xTimer:定时器句柄
 * xNewPeriod:超时Tick
 * xTicksToWait:等待Tick
 */
xTimerChangePeriod(xTimer, xNewPeriod, xTicksToWait) 更改软件定时器的定时超时时间
xTimerChangePeriodFromISR() 在中断中更改定时超时时间时

实验
Task1:使用KEY1开启软件定时器,使用KEY2停止软件定时器
定时器超时函数:每隔500ms打印一次
FreeRTOSConfig.h文件添加的代码如下

/* 软件定时器相关定义 */
#define configUSE_TIMERS 1 /* 1: 使能软件定时器, 默认: 0。使能后需指定下面3个 */
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) /* 定义软件定时器任务的优先级 */
#define configTIMER_QUEUE_LENGTH 5 /* 定义软件定时器命令队列的长度*/
#define configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE /* 定义软件定时器任务的栈空间大小*/

freertos_demo.c文件添加的代码如下

#include "freertos_demo.h"

void Task1(void * pvParameters);
void TimerCallback(TimerHandle_t xTimer);
/**
 * 入口函数:创建启动任务,启动调度器
 */
TaskHandle_t Task1_Handler,Task2_Handler;   //任务控制块指针
TimerHandle_t Timer_Handle;                 //软件定时器句柄
void FreeRTOS_Start(void)
{   
    Timer_Handle = xTimerCreate("SoftTimer",500,pdTRUE,NULL,TimerCallback);//动态方式创建软件定时器
   
    /* 进入临界区:防止中断打断 */
    vPortEnterCritical();
    
    /* 1、创建任务1 */
    xTaskCreate((TaskFunction_t)Task1,"Task1",128,NULL,1,&Task1_Handler);//优先级为1

    /* 退出临界区 */
    vPortExitCritical();
    
    /* 2、启动调度器 */
    vTaskStartScheduler();//启动了任务调度器,任务就开始运行了,且创建空闲任务。
}

/**
 * 任务函数1:使用KEY定时器的开关
 */
void Task1(void * pvParameters)
{
    while (1)
    {
        if(Key_Scan() == 1)//按键按下
        {
            printf("启动定时器\r\n");
            xTimerStart(Timer_Handle, 0);//启动定时器服务函数
        }
        else if(Key_Scan() == 2)
        {
            printf("关闭定时器\r\n");
            xTimerStop(Timer_Handle, 0);//关闭定时器服务函数
        }
    }
}

/**
 * 定时器超时服务函数
 */
void TimerCallback(TimerHandle_t xTimer)
{
    printf("定时到了!\r\n");
}

在这里插入图片描述

综上:创建出Task1,和软件定时器服务任务后,虽然软件定时器的服务任务优先级大于Task1,但是由于还没有启动定时器服务任务,所以一直执行Task1。当启动后,定时设置的是500Tick,则启动后先将服务任务阻塞500Tick,然后500Tick后恢复,然后去抢占Tick1,执行软件定时器服务任务。大概的软件定时器服务任务函数如下:

void TimerControl(void)
{
    vTaskDelay(TickType_t Ticks);//先阻塞Tick
    ...
    ...
    pxCallbackFunction(TimerHandle_t xTimer);//然后去执行回调函数
}

2、中断管理

2.1、中断屏蔽

   在STM32中,中断优先级是通过中断优先级配置寄存器的高4位 [7:4] 来配置的。因此STM32支持最多16级中断优先级,其中数值越小表示优先级越高,即更紧急的中断。
  FreeRTOS中,将PendSV和SysTick设置最低中断优先级(数值最大,15),保证系统任务切换不会阻塞系统其他中断的响应。
  FreeRTOS利用BASEPRI寄存器实现中断管理,屏蔽优先级低于某一个阈值的中断。比如: BASEPRI设置为0x50(只看高四位,也就是5),代表中断优先级在5 ~ 15内的均可被屏蔽,0~4的中断优先级正常执行。
  而BASEPRI寄存器通过configMAX_SYSCALL_INTERRUPT_PRIORITY宏来设置的,当设置为5时,则0~4中断正常执行,不会被FreeRTOS管理。而5 ~ 15的中断会被FreeRTOS管理。因此可以调用API函数来屏蔽5 ~ 15的中断。
【注】使用configMAX_SYSCALL_INTERRUPT_PRIORITY将BASEPRI配置为0x50后,也只有5~15优先级的中断调用xxxFromISR函数才能执行。
在这里插入图片描述中断屏蔽API

在 portmacro.h 中有定义,如下:
#define portDISABLE_INTERRUPTS()                  vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS()                   vPortSetBASEPRI( 0 )

调用portDISABLE_INTERRUPTS() ,FreeRTOS会关闭管理的所有中断。
调用portENABLE_INTERRUPTS() ,FreeRTOS会打开管理的所有中断。

Q

2.2、临界区

临界段代码,又称为临界区,指的是那些必须在不被打断的情况下完整运行的代码段。例如,某些外设的初始化可能要求严格的时序,因此在初始化过程中不允许被中断打断。
进入/退出临界区API

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

taskENTER_CRITICAL() :进入临界段。本质是去调用portDISABLE_INTERRUPTS() 。因此使用configMAX_SYSCALL_INTERRUPT_PRIORITY将BASEPRI配置为0x50后。然后进入临界区后5~15的中断都不会打断之后代码的执行(PendSV和SysTick也会停止,任务调度也会停止),直到退出临界区。
【注意】调用多少次进入临界区,就要调用多少次退出临界区。这样才能真正的退出临界区。
实验
设置管理的优先级范围:5~15。
使用两个定时器,一个优先级为4,一个优先级为6。
两个定时器每1s,打印一段字符串,当关中断时,停止打印,开中断时持续打印。

FreeRTOSConfig.h文件中添加如下的代码

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

freertos_demo.c文件的代码如下

#include "freertos_demo.h"

void Task1(void);

TaskHandle_t Task1_Handler;
void FreeRTOS_Start(void)
{
    /* 进入临界区:防止中断打断 */
    vPortEnterCritical();
    /* 1、创建任务1 */
    xTaskCreate((TaskFunction_t)Task1,"Task1",128,NULL,1,&Task1_Handler);
    
    /* 退出临界区 */
vPortExitCritical();

/* 2、启动调度器 */
    vTaskStartScheduler();

}

/**
 * 任务1函数:开关中断
 */
void Task1(void)
{
    uint8_t Key = 0;
    while (1)
    {
        Key = Key_Scan();
        /*关闭中断*/
        if(Key == 1)
        {
            printf("关闭中断!\r\n");
            //taskENTER_CRITICAL();
            portDISABLE_INTERRUPTS(); //进入临界区,关闭优先级5~15的所有中断
        }else if(Key == 2)
        {
            printf("开启中断!\r\n");
            //taskEXIT_CRITICAL();
            portENABLE_INTERRUPTS();//退出临界区,开启优先级5~15的所有中断

        }  
//vTaskDelay(500); //这里不能有延时,因为代码底层有关闭中断和开启中断
    }
}

stm32f1xx_it.c文件的代码如下:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM4) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */
  else if(htim->Instance == TIM2)//是定时器2
  {
       printf("TIM2的优先级为4!\r\n");
  }
  else if(htim->Instance == TIM3)//是定时器3
  {
       printf("TIM3的优先级为6!\r\n");
   }
  /* USER CODE END Callback 1 */
}

在这里插入图片描述

【注意】此实验将SysTick中断的优先级配置 < 5。不然在进入临界区后想退出临界区就不可能了。因为若Tick中断依然是15,进入临界区就会关闭Tick中断,即FreeRTOS代码就停止运行了,按下KEY2将无任何反应。


原文地址:https://blog.csdn.net/qq_51284092/article/details/145143459

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!