FreeRTOS—中断管理
文章目录
- 一、中断
- 1.1.中断的简介
- 1.2.中断优先级分组设置
- 1.3.中断相关寄存器
- 1.3.1.三个系统中断优先级配置寄存器
- 1.3.2.配置 PendSV 和 SysTick 中断优先级
- 1.3.3.三个中断屏蔽寄存器
- 二、实验
- 2.1.实验设计
- 2.2.软件设计
一、中断
1.1.中断的简介
中断就是让 CPU 打断正常运行的程序,转而去处理紧急的事件;中断执行机制可分为简单的三步:
- 中断请求:外设产生中断(GPIO 外部中断、定时器中断等等)
- 响应中断:CPU 停止执行当前程序,转而去执行中断处理程序( ISR )
- 执行完毕,返回被打断的程序处,继续往下执行
1.2.中断优先级分组设置
ARM Cortex-M 使用了 8 位宽的寄存器来配置中断的优先等级,这个寄存器就是中断优先等级配置寄存器,但 STM32 只用了中断优先级配置寄存器的高 4 位( Bit4 - Bit7 ),所以 STM32 提供了最大 16 级的中断优先等级。
STM32 的中断优先级可以分为抢占优先级和子优先级:
- 抢占优先级:抢占优先级高的中断可以打断正在执行但是抢占优先级低的中断
- 子优先级:当同时发生具体相同抢占优先级的两个中断时,子优先级数值小的优先执行
中断优先级分组一共有五组,可以通过调用HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_x)
,其中中断优先级数值越小,越优先执行。
优先级分组 | 抢占优先级 | 子优先级 | 优先级配置寄存器的高 4 位 |
---|---|---|---|
NVIC_PriorityGroup_0 | 0 级抢占优先级 | 0 - 15 级子优先级 | 0bit 用于抢占优先级,4bit用于子优先级 |
NVIC_PriorityGroup_1 | 0 - 1 级抢占优先级 | 0 - 7 级子优先级 | 1bit 用于抢占优先级,3bit用于子优先级 |
NVIC_PriorityGroup_2 | 0 - 3 级抢占优先级 | 0 - 3 级子优先级 | 2bit 用于抢占优先级,2bit用于子优先级 |
NVIC_PriorityGroup_3 | 0 - 7 级抢占优先级 | 0 - 1 级子优先级 | 3bit 用于抢占优先级,1bit用于子优先级 |
NVIC_PriorityGroup_4 | 0 - 15 级抢占优先级 | 0 级子优先级 | 4bit 用于抢占优先级,0bit用于子优先级 |
如果要调用 FreeRTOS 的 API 函数里使用中断,就要注意:
- 低于
configMAX_SYSCALL_INTERRUPT_PRIORITY
优先级的中断里才允许调用 FreeRTOS的 API 函数 - 建议将所有优先级位指定为抢占优先级位,方便 FreeRTOS 管理(调用函数
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)
) - 中断优先级数值越小越优先,任务优先级数值越大越优先
1.3.中断相关寄存器
1.3.1.三个系统中断优先级配置寄存器
这三个寄存器分别为:
- SHPR1 寄存器地址:0xE000ED18
- SHPR2 寄存器地址:0xE000ED1C
- SHPR3 寄存器地址:0xE000ED20
一个寄存器占 4 个地址,一个地址就有 8 个位,这样就组成了一个有 32 位的寄存器。如下图中,SysTick 用于单片机的心跳节拍,PendSV 用于控制任务切换或任务调度等。如果想操作 PendSV 的优先级,就把 SHPR3 寄存器的地址偏移 16 位;控制 SysTick 的优先级就偏移 24 位。
1.3.2.配置 PendSV 和 SysTick 中断优先级
通过下图,寄存器内部的操作,实现了 PendSV 和 SysTick 设置在最低级,设置最低级保证了系统任务切换不会阻塞系统其他中断的响应。
偏移 8 - 4 位,意思是低 4 位无效。
1.3.3.三个中断屏蔽寄存器
这三个寄存器分别为:
- PRIMASK:这是个只有 1 位的寄存器,当它置 1 时,就关闭所有可屏蔽的异常,只剩下 NMI 和 fault 可以响应;置为 0 表示没有关闭中断
- FAULTMASK:这是个只有 1 位的寄存器,当它置 1 时,只有 NMI 才能响应,所有其他的异常包括中断和 fault 都关闭;置为 0 表示没有关闭中断
- BASEPRI:这个寄存器最多有 9 位(由表达优先级的位数决定),它定义了被屏蔽优先级的阈值,当它被设置成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优先级越低);置为 0 表示没有关闭中断
FreeRTOS 所使用的中断管理就是利用 BASEPRI 这个寄存器,BASEPRI 就是屏蔽优先级低于设定阈值的中断。例如:BASEPRI 设置为 0x50,代表中断优先级在 5 ~ 15 内的均被屏蔽,0 ~ 4 的中断优先级正常执行。
二、实验
2.1.实验设计
设计两个定时器,一个优先级为 4,一个优先级为 6(系统所管理的优先级范围:5 ~ 15);两个定时器的现象:每 1s 打印一段字符串,当关中断时,停止打印,开中断时持续打印。本实验将设置两个任务:
- start_task:用来创建 task1 任务
- task1:中断测试任务,任务中将调用关中断和开中断函数来体现对中断的管理作用
2.2.软件设计
在定时器 TIMER 文件夹里的 btim.c 文件,初始化定时器 6 和定时器 7 ,其中定时器 6 的抢占优先级设置为 6,定时器 7 的抢占优先级设置为 4。
下面是 btim.c 代码:
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/btim.h"
#include "./SYSTEM/usart/usart.h"TIM_HandleTypeDef g_tim6_handle;
TIM_HandleTypeDef g_tim7_handle; //初始化定时器6
void btim_tim6_int_init(uint16_t arr, uint16_t psc)
{BTIM_TIMX_INT_CLK_ENABLE(); g_tim6_handle.Instance = BTIM_TIM6_INT; g_tim6_handle.Init.Prescaler = psc; g_tim6_handle.Init.CounterMode = TIM_COUNTERMODE_UP; g_tim6_handle.Init.Period = arr; g_tim6_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(&g_timx_handle);HAL_NVIC_SetPriority(BTIM_TIM6_INT_IRQn, 6, 0);HAL_NVIC_EnableIRQ(BTIM_TIM6_INT_IRQn); HAL_TIM_Base_Start_IT(&g_tim6_handle);
}//初始化定时器7
void btim_tim7_int_init(uint16_t arr, uint16_t psc)
{BTIM_TIM7_INT_CLK_ENABLE(); g_tim7_handle.Instance = BTIM_TIM7_INT;g_tim7_handle.Init.Prescaler = psc; g_tim7_handle.Init.CounterMode = TIM_COUNTERMODE_UP; g_tim7_handle.Init.Period = arr; g_tim7_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(&g_tim7_handle);HAL_NVIC_SetPriority(TIM7_IRQn, 4, 0); HAL_NVIC_EnableIRQ(TIM7_IRQn); HAL_TIM_Base_Start_IT(&g_tim7_handle);
}//定时器6的中断服务函数
void BTIM_TIM6_INT_IRQHandler(void)
{HAL_TIM_IRQHandler(&g_tim6_handle);
}//定时器7的中断服务函数
void BTIM_TIM7_INT_IRQHandler(void)
{HAL_TIM_IRQHandler(&g_tim7_handle);
}//定时器更新中断回调函数,参数是定时器句柄指针
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if (htim == (&g_tim6_handle)){printf("666\r\n");}else if(htim == (&g_tim7_handle)){printf("7777\r\n");}
}
下面是 btim.h 代码:
#ifndef __BTIM_H
#define __BTIM_H#include "./SYSTEM/sys/sys.h"#define BTIM_TIM6_INT TIM6
#define BTIM_TIM6_INT_IRQn TIM6_DAC_IRQn
#define BTIM_TIM6_INT_IRQHandler TIM6_DAC_IRQHandler
#define BTIM_TIM6_INT_CLK_ENABLE() do{ __HAL_RCC_TIM6_CLK_ENABLE(); }while(0) #define BTIM_TIM7_INT TIM7
#define BTIM_TIM7_INT_IRQn TIM7_IRQn
#define BTIM_TIM7_INT_IRQHandler TIM7_IRQHandler
#define BTIM_TIM7_INT_CLK_ENABLE() do{ __HAL_RCC_TIM7_CLK_ENABLE(); }while(0) void btim_tim6_int_init(uint16_t arr, uint16_t psc);
void btim_tim7_int_init(uint16_t arr, uint16_t psc);#endif
下面是 freertos_demo.c 代码:
#include "freertos_demo.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"/*FreeRTOS*********************************************************************************************/#include "FreeRTOS.h"
#include "task.h"/******************************************************************************************************/#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task(void *pvParameters);#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1(void *pvParameters);/******************************************************************************************************/
//入口函数
void freertos_demo(void)
{xTaskCreate((TaskFunction_t) start_task,(char*) "start_task",(uint16_t) START_TASK_STACK_SIZE,(void*) NULL,(UBaseType_t) START_TASK_PRIO,(TaskHandle_t*) &start_task_handler);vTaskStartScheduler();
}//start_task
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t) task1, (char*) "task1",(uint16_t) TASK1_STACK_SIZE,(void*) NULL,(UBaseType_t) TASK1_PRIO,(TaskHandle_t*) &task1_handler);vTaskDelete(NULL); taskEXIT_CRITICAL();
}//task1,num每到5就关一次中断,隔5秒就开中断
void task1(void *pvParameters)
{uint32_t num = 0;while(1){if(++num == 5){num = 0;printf("关中断!!\r\n");portDISABLE_INTERRUPTS();delay_ms(5000); //为什么这里不使用FreeRTOS的延迟函数呢?//因为FreeRTOS的延迟函数里面会调用使能中断的函数,也就是下一句的函数,相当于你关了中断,又立刻打开中断,这样实验就没意义了printf("开中断\r\n");portENABLE_INTERRUPTS();}vTaskDelay(1000);}
}
最后的实验现象如下图: