FreeRTOS的中断管理
在STM32中,中断优先级是通过中断优先级配置寄存器的高4位 [7:4] 来配置的。因此STM32支持最多16级中断优先级,其中数值越小表示优先级越高,即更紧急的中断。(任务调度的任务优先级相反,是数值越大越优先)
FreeRTOS可以与STM32原生的中断机制结合使用,但它提供了自己的中断管理机制,主要是为了提供更强大和灵活的任务调度和管理功能。
FreeRTOS中,将PendSV和SysTick设置最低中断优先级(数值最大,15),保证系统任务切换不会阻塞系统其他中断的响应。
FreeRTOS利用BASEPRI寄存器实现中断管理,屏蔽优先级低于某一个阈值的中断。比如: BASEPRI设置为0x50(只看高四位,也就是5),代表中断优先级在5–15内的均被屏蔽,0–4的中断优先级正常执行。这个也就是5–15的优先级被FreeRTOS管着,0~4还是归stm32管着。
在中断服务函数中调用FreeRTOS的API函数需注意:
- 中断服务函数的优先级需在FreeRTOS所管理的范围内,阈值由configMAX_SYSCALL_INTERRUPT_PRIORITY指定。
- 建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理。
- 在中断服务函数里边需调用FreeRTOS的API函数,必须使用带“FromISR”后缀的函数。
FreeRTOS的开关中断
关中断即关掉中断,中断不起作用了,一个正在执行的程序不会因为突发事件被打断。
开中断即打开中断,中断重新起作用。
FreeRTOS 开关中断函数其实是宏定义,在 portmacro.h 中有定义,如下:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
为什么要有关中断和开中断?
简单理解为,不想让某一个执行的程序被打断,这个执行的程序一旦被打断(比如初始化程序),初始化都还没执行完毕,再去执行其他,又怎么会执行成功呢?
FreeRTOS的临界段代码
学过操作系统的都知道,临界资源的概念即同一个时间段只允许一个进程访问的资源,对临界资源必须互斥地访问。
临界资源要实现互斥访问,逻辑上分为四个部分:
- 进入区,检查是否可以进入临界区,理解为上锁,而FreeRTOS中上锁就是关中断。
- 临界区
- 退出区,解锁即开中断。
- 剩余区
临界段代码,又称为临界区,指的是那些必须在不被打断的情况下完整运行的代码段。
不知道也没关系。你将临界段当成打游戏最激烈的那段时间,你不希望有人打扰,所以你选择锁门和戴上耳机,这个相当于关中断。关掉之后,接下来的时间谁都不能打扰我直到游戏完毕,然后取掉耳机打开门(开中断),饱餐一顿。
在FreeRTOS中,进入临界段代码时需要关闭中断,在处理完临界段代码后再重新开启中断。
FreeRTOS系统本身包含许多临界段代码,并对其进行了保护。在编写用户程序时,有些情况下也需要添加临界段代码以确保代码的完整性。
与临界段代码保护有关的函数有 4 个:
1、taskENTER_CRITICAL() :进入临界段。
2、taskEXIT_CRITICAL() :退出临界段。
3、taskENTER_CRITICAL_FROM_ISR() :进入临界段(中断级)。
4、taskEXIT_CRITICAL_FROM_ISR():退出临界段(中断级)。
进入和退出临界段是成对使用的。
前面说过,进入临界区要上锁,也就是关中断,在freertos的代码中可以表现。
FreeRTOS中断管理实验
任务要求:
- 设置管理的优先级范围:5~15。
- 使用两个定时器,一个优先级为4,一个优先级为6。
- 两个定时器每1s,打印一段字符串,当关中断时,停止打印,开中断时持续打印。
在动态创建任务的基础上进行修改的。
在Hal库添加两个定时器
添加完定时器,记得重新注释掉stm32f1xx_it.c的SVC_Handler和PendSV_Handler函数。这个是为了用FreeRTOS实现的函数
定时器中断函数
首先,在main函数中书写定时器中断函数
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 BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_Base_Start_IT(&htim3);
/* USER CODE END 2 */
添加配置文件
在FreeRTOSConfig.h里面添加
/* 中断嵌套行为配置 */
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 /* 中断最低优先级 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /* FreeRTOS可管理的最高中断优先级 */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_API_CALL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY
添加后编译会出现
这是因为多重宏定义FreeRTOSConfig.h已经存在了相同的宏,只需要注释一下
函数具体实现
默认FreeRTOS官方文件已经配置完毕。
任务宏定义
/* 启动任务的参数配置 */
#define START_STACK_DEPTH 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handler;
void Start_Task(void *pvParameters);
/* 任务1的参数配置 */
#define TASK1_STACK_DEPTH 128
#define TASK1_TASK_PRIORITY 2
TaskHandle_t task1_handler;
void Task1(void *pvParameters);
启动函数和启动任务
void FreeRTOS_Start(void)
{
/* 1.创建启动任务 */
xTaskCreate((TaskFunction_t) Start_Task,
(char *) "Start_Task",
(configSTACK_DEPTH_TYPE) START_STACK_DEPTH,
(void *) NULL,
(UBaseType_t)START_TASK_PRIORITY ,
(TaskHandle_t * ) &start_task_handler );
/* 2.启动调度器 */
vTaskStartScheduler();
}
//启动任务
void Start_Task(void* pvParameters )
{
//进入临界区
//vPortEnterCritical();
taskENTER_CRITICAL();
/* 创建任务 1*/
xTaskCreate((TaskFunction_t) Task1,
(char *) "Task1",
(configSTACK_DEPTH_TYPE) TASK1_STACK_DEPTH,
(void *) NULL,
(UBaseType_t)TASK1_TASK_PRIORITY ,
(TaskHandle_t * ) &task1_handler );
/* 启动任务只需要执行一次,用完就删 */
vTaskDelete(NULL);
//退出临界区
//vPortExitCritical();
taskEXIT_CRITICAL();
}
任务1函数实现:
void Task1(void* pvParameters )
{
uint32_t delay=0;
while (1)
{
delay=50000000;
/* 关中断:观察tim的打印情况 */
printf("关中断..............");
portDISABLE_INTERRUPTS();
/* 加个延时,观察 */
while(delay--);
/* 开中断:观察tim的打印情况 */
printf("开中断..............");
portENABLE_INTERRUPTS();
}
}
完整代码
#include "freertos_demo.h"
/* freertos相关的头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 任务需要用到 */
#include "LED.h"
#include "Key.h"
#include "usart.h"
/* 启动任务的参数配置 */
#define START_STACK_DEPTH 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handler;
void Start_Task(void *pvParameters);
/* 任务1的参数配置 */
#define TASK1_STACK_DEPTH 128
#define TASK1_TASK_PRIORITY 2
TaskHandle_t task1_handler;
void Task1(void *pvParameters);
/**
* @description:入口函数,创建启动任务,启动调度器
* @return{}
*/
void FreeRTOS_Start(void)
{
/* 1.创建启动任务 */
xTaskCreate((TaskFunction_t) Start_Task,
(char *) "Start_Task",
(configSTACK_DEPTH_TYPE) START_STACK_DEPTH,
(void *) NULL,
(UBaseType_t)START_TASK_PRIORITY ,
(TaskHandle_t * ) &start_task_handler );
/* 2.启动调度器 */
vTaskStartScheduler();
}
/**
* @description:启动任务,创建其他任务
* @return{}
*/
void Start_Task(void* pvParameters )
{
//进入临界区
//vPortEnterCritical();
taskENTER_CRITICAL();
/* 创建任务 1*/
xTaskCreate((TaskFunction_t) Task1,
(char *) "Task1",
(configSTACK_DEPTH_TYPE) TASK1_STACK_DEPTH,
(void *) NULL,
(UBaseType_t)TASK1_TASK_PRIORITY ,
(TaskHandle_t * ) &task1_handler );
/* 启动任务只需要执行一次,用完就删 */
vTaskDelete(NULL);
//退出临界区
//vPortExitCritical();
taskEXIT_CRITICAL();
}
/**
* @description:两个定时器每1s,打印一段字符串,当关中断时,停止打印,开中断时持续打印。
* @return{}
*/
void Task1(void* pvParameters )
{
uint32_t delay=0;
while (1)
{
delay=50000000;
/* 关中断:观察tim的打印情况 */
printf("关中断..............");
portDISABLE_INTERRUPTS();
/* 加个延时,观察 */
while(delay--);
/* 开中断:观察tim的打印情况 */
printf("开中断..............");
portENABLE_INTERRUPTS();
}
}
结果展示
下载程序后,定时器每s进一次中断,因为定时器3的优先级高,所以先打印定时器2,然后打印定时器3。
进入关中断后,因为FreeRTOS只管理5~15的优先级中断,所以定时器3被关掉了,定时器2不归FreeRTOS管理,只打印定时器2。
等到一段时间后开中断,两个定时器都开始工作。