软件定时器+中断管理
- 1、软件定时器
- 2、中断管理
- 2.1、中断屏蔽
- 2.2、临界区
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将无任何反应。