在嵌入式系统开发中,中断管理是一个至关重要的概念。它允许我们的系统能够及时响应外部事件,而不需要通过轮询的方式来浪费宝贵的处理器资源。FreeRTOS作为一款广泛应用的实时操作系统,它提供了灵活且高效的中断管理机制,可以帮助我们更好地处理这些外部事件。
目录
一、什么是中断?
1.1 中断的概念
1.2 中断执行机制
1.3 Cortex-M4的中断资源
1.4 两个重要的内核中断
1.4.1 PendSV中断
1.4.2 Systick
二、中断优先级分组设置(熟悉)
2.1 外部中断
2.2 内部中断
2.3 中断优先级分组设置
2.3.1 优先级分组概念
2.3.1 FreeRTOS优先级分组配置
2.3.2 特点
三、中断相关寄存器(熟悉)
3.1 系统中断优先级配置寄存器
3.2 FreeRTOS如何配置PendSV和Systick中断优先级?
3.2 中断屏蔽寄存器
四、FreeRTOS中断管理实验(掌握)编辑
一、什么是中断?
1.1 中断的概念
让CPU打断正常运行的程序,转而去处理紧急的事件(中断服务函数ISR),当中断事件处理完毕后,处理器可以恢复到中断前的状态,继续执行之前的程序,就叫中断。
1.2 中断执行机制
中断执行机制,可简单概括为三步:
1.3 Cortex-M4的中断资源
Cortex-M4内核支持256个中断,包含16个系统(内核)中断和240个外部中断。但是芯片厂商⼀般不会把内核的这些资源全部用完。经过裁剪得到自己厂家的芯片。内核中断如下所示:
经过裁剪,STM32F407具有82个可屏蔽中断通道。10个系统中断,如下图所示:
1.4 两个重要的内核中断
1.4.1 PendSV中断
在FreeRTOS中,PendSV(Pended System Service Call)是一种专门的中断,用于任务切换。它是Cortex-M系列处理器(如ARM Cortex-M3、M4、M7等)提供的一种系统级中断,其优先级最低,以确保它只在所有其他中断处理完毕后才执行。
PendSV的作用:
PendSV的主要作用是实现上下文切换,即从一个任务切换到另一个任务。这是FreeRTOS实现多任务调度的核心机制之一。具体来说,当需要进行任务切换时,FreeRTOS会设置PendSV中断挂起(pend),以便在下一个合适的时间点执行任务切换。
PendSV的优点
优先级最低:由于PendSV中断的优先级最低,确保了它不会打断任何其他正在处理的中断,从而保证了系统的稳定性和中断处理的完整性。
简化任务切换:使用PendSV中断进行任务切换简化了操作系统内核的设计,因为任务切换逻辑被集中在一个统一的中断服务例程中。
效率高:PendSV中断通过硬件机制实现,效率高且开销小,适用于实时操作系统的需求。
1.4.2 Systick
在FreeRTOS中,SysTick(系统定时器)是一种用于实现系统节拍(tick)的定时器。它是ARM Cortex-M系列处理器中的一个内置定时器,用于提供周期性中断,从而驱动操作系统的时钟节拍。这种周期性中断对操作系统的调度器至关重要,因为它决定了任务的时间片、延时操作和其他时间相关的功能。
SysTick的作用
生成系统节拍(tick)中断:SysTick定时器周期性地产生中断信号,每次中断称为一个"tick"。这个节拍频率通常被配置为1毫秒一次,但可以根据需要进行调整。
驱动调度器:每个tick中断都会触发FreeRTOS调度器的运行,检查是否有需要调度的任务。例如,如果一个任务的延时时间结束,调度器会将其状态从阻塞(blocked)变为就绪(ready)。
时间管理:SysTick中断用于实现FreeRTOS中的各种时间管理功能,如任务延时(vTaskDelay)、定时器服务(software timers)、和时间片轮转(time slicing)等。
SysTick的工作流程
初始化SysTick:在系统启动时,FreeRTOS会配置SysTick定时器的频率,使其以设定的周期产生中断。这个配置通常在FreeRTOS的初始化代码中完成。
产生中断:SysTick定时器按照设定的周期(例如,每1毫秒)产生中断信号。
中断处理:每次SysTick中断发生时,处理器会跳转到SysTick中断服务例程(ISR)。在FreeRTOS中,SysTick中断服务例程通常会调用
xPortSysTickHandler()
函数。调度任务:在SysTick中断服务例程中,FreeRTOS的调度器会执行任务调度逻辑,包括检查延时任务、管理时间片、更新系统时钟等。如果有需要切换的任务,会通过PendSV中断来进行上下文切换。
SysTick在FreeRTOS中扮演着关键角色,它通过周期性中断驱动操作系统的时钟节拍,使得任务调度、延时管理和时间片轮转等功能得以实现。利用Cortex-M系列处理器内置的SysTick定时器,FreeRTOS能够高效且准确地管理系统时间,确保实时操作系统的各项功能正常运行。
二、中断优先级分组设置(熟悉)
2.1 外部中断
Cortex-M处理器有多个用于管理中断的可编程寄存器。这些寄存器大多数在NVIC(嵌套向量中断控制器,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组。)和系统控制 块(SCB)中。
IP [240](Interrupt Priority Registers):中断优先级控制寄存器组。由240个8bit的寄存器组成,每个可屏蔽中断占⽤8bit。由于我们只有82个可屏蔽中断通道,因此我们只⽤IP[81] - IP[0]这82个。并且,每个中断通道占用的 8bit并没有全部使用,而是只用了高4位。我们知道STM32的中断资源由NVIC来统⼀管理,而每个中断的优先级是由优先级寄存器IP 控制的,并且每⼀个8bit的寄存器对应⼀个中断。但是,只占用了高四位。 然后我们对这4位又进行了划分,分为高n位的抢占优先级和低4-n位的响应优先级。NVIC里面的中断优先级控制寄存器组IP可以对所有的外部中断进行优先级设置。
2.2 内部中断
在ARM Cortex-M处理器中,系统处理优先级(System Handler Priority, SHP)寄存器用于设置系统异常/内核中断(如硬故障、SVCall、SysTick等)的优先级。SHP寄存器是ARM Cortex-M处理器内核的一部分,不是FreeRTOS特有的。它们用于配置特定系统异常/内部中断的优先级。
2.3 中断优先级分组设置
2.3.1 优先级分组概念
学过裸机开发,我们知道 ARM Cortex-M 使用了 8 位宽的寄存器来配置中断的优先等级(0-255),这个寄存器就是中断优先级配置寄存器IP,但STM32,只用了中断优先级配置寄存器的高4位 [7 : 4],所以STM32提供了最大16级的中断优先等级。注意:中断优先级数值越小,优先级越高。
STM32 的中断优先级可以分为抢占优先级和响应优先级 :
- 抢占优先级: 抢占优先级高的中断可以打断正在执行但抢占优先级低的中断。
- 响应优先级:当同时发生具有相同抢占优先级的两个中断时,子优先级数值小的优先执行,如果两个相同抢占优先级的中断不是同时发生,那么是不会发生抢占的,等先发生中断的执行完,才会去执行后来的中断。
2.3.1 FreeRTOS优先级分组配置
一共有 5 种分配方式,对应着中断优先级分组的 5 个组 :
2.3.2 特点
1、低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级(也就是处于5-15优先级)的中断里才允许调用FreeRTOS 的API函数;FreeRTOS能管理的最高的优先级。
2、建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理。
(调用函数) NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
3、中断优先级数值越小越优先,任务优先级数值越大越优先。
三、中断相关寄存器(熟悉)
3.1 系统中断优先级配置寄存器
在系统控制块SCB结构体存在一个系统处理优先级(System Handler Priority, SHP)寄存器用于设置系统异常/内核中断(如硬故障、SVCall、SysTick等)的优先级。SHP寄存器是ARM Cortex-M处理器内核的一部分,不是FreeRTOS特有的。它们用于配置特定系统异常/内部中断的优先级。
四个8位的寄存器组成⼀个32位的寄存器,所以,pendsv和systick的控制寄存器为:0XE000ED20 的次⾼8位和⾼8位。
3.2 FreeRTOS如何配置PendSV和Systick中断优先级?
- #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY :此宏⽤来设置Free RTOS系 统可管理的最⼤优先级。⾼于此宏设置的优先级不受Free RTOS的管理。
- #define configKERNEL_INTERRUPT_PRIORITY⽤来设置内核中断优先级:PendSV 和 SYSTICK的中断优先级 (左移4位是因为:中断优先级IP只有⾼4位有效)
通过上述配置,将PendSV和SysTick设置中断优先级中的最低优先级,设置最低:保证系统任务切换不会阻塞系统其他中断的响应,为什么?中断是非常紧急的事情,也就是说中断可以打断任意的任务,而任务不可以打断中断,PendSV就是用来处理任务切换的,因此要把PendSV任务切换中断的优先级配置为最低优先级。
3.2 中断屏蔽寄存器
三个中断屏蔽寄存器,分别为 PRIMASK、 FAULTMASK 和BASEPRI
FreeRTOS所使用的中断管理就是利用的BASEPRI这个寄存器!!BASEPRI:屏蔽优先级低于某一个阈值的中断。比如: BASEPRI设置为0x50,代表中断优先级在5~15内的均被屏蔽,0~4的中断优先级正常执行
BASEPRI:屏蔽优先级低于某一个阈值的中断,当设置为0时,则不关闭任何中断,也就是任何中断都可以响应。
关中断程序示例:
中断优先级在5 ~ 15的中断全部被关闭,而中断优先级在0-4的中断不会受影响!
FreeRTOS中断管理就是利用BASEPRI寄存器实现的,屏蔽掉一定范围内的中断!
四、FreeRTOS中断管理实验(掌握)
定时器6和定时器7的初始化配置
#include "stm32f4xx.h" // Device header
#include "mytim.h"
#include "stdio.h"/************配置基本定时器6和基本定时器7**************/
void Interrupt_Init(void)
{//1.开启TIM时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE); //开启时钟使能RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7,ENABLE); //开启时钟使能//设置中断优先级分组(FreeRTOS利用的是抢占式调度)NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//2.配置中断,配置NVICNVIC_InitTypeDef Struct;Struct.NVIC_IRQChannel = TIM6_DAC_IRQn;Struct.NVIC_IRQChannelCmd =ENABLE;Struct.NVIC_IRQChannelPreemptionPriority = 6; //基本定时器6抢占优先级6Struct.NVIC_IRQChannelSubPriority = 0; //基本定时器6响应优先级0NVIC_Init(&Struct); NVIC_InitTypeDef Struct1;Struct1.NVIC_IRQChannel = TIM7_IRQn;Struct1.NVIC_IRQChannelCmd =ENABLE;Struct1.NVIC_IRQChannelPreemptionPriority = 4; //基本定时器7抢占优先级4Struct1.NVIC_IRQChannelSubPriority = 0; //基本定时器7响应优先级0NVIC_Init(&Struct1); //3.配置TIM(时基单元)TIM_TimeBaseInitTypeDef Struct2;Struct2.TIM_Period = 9999; //自动重载寄存器ARR:定时1秒Struct2.TIM_Prescaler = 8399; //预分频器PSCTIM_TimeBaseInit(TIM6,&Struct2); //初始化时基单元TIM_TimeBaseInitTypeDef Struct3;Struct3.TIM_Period = 9999; //自动重载寄存器ARR:定时1秒Struct3.TIM_Prescaler = 8399; //预分频器PSCTIM_TimeBaseInit(TIM7,&Struct3); //初始化时基单元TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE); //定时中断配置 TIM_Cmd(TIM6,ENABLE); //使能定时器TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE); //定时中断配置 TIM_Cmd(TIM7,ENABLE); //使能定时器
}//4.重写定时器6中断服务函数
void TIM6_DAC_IRQHandler(void)
{if(TIM_GetITStatus(TIM6,TIM_IT_Update) == SET){//具体实现printf("TIM6优先级为6的定时器正在运行!\n");TIM_ClearITPendingBit(TIM6,TIM_IT_Update); //清除中断标志位}
}//4.重写定时器7中断服务函数
void TIM7_IRQHandler(void)
{if(TIM_GetITStatus(TIM6,TIM_IT_Update) == SET){//具体实现printf("TIM7优先级为4的定时器正在运行!!!!\n");TIM_ClearITPendingBit(TIM7,TIM_IT_Update); //清除中断标志位}
}
创建任务及实现任务函数:
#include "stm32f4xx.h" // Device header
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"
#include "mydelay.h"/**********************START_TASK任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/#define START_TASK_STACK_SIZE 128 //定义堆栈大小为128字(1字等于4字节)
#define START_TASK_PRIO 1 //定义任务优先级,0-31根据任务需求
TaskHandle_t start_task_handler; //定义任务句柄(结构体指针)
void start_task(void* args);/**********************TASK1任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define TASK1_STACK_SIZE 128 //定义堆栈大小为128字(1字等于4字节)
#define TASK1_PRIO 2 //定义任务优先级,0-31根据任务需求
TaskHandle_t task1_handler; //定义任务句柄(结构体指针)
void task1(void* args);/*********开始任务用来创建一个任务,只创建一次,不能是死循环,创建完这个任务后删除开始任务本身***********/
void start_task(void* args)
{taskENTER_CRITICAL(); /*进入临界区*/xTaskCreate( (TaskFunction_t) task1,(char *) "task1", ( configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,(void *) NULL,(UBaseType_t) TASK1_PRIO ,(TaskHandle_t *) &task1_handler );vTaskDelete(NULL); //删除开始任务自身,传参NULLtaskEXIT_CRITICAL(); /*退出临界区*///临界区内不会进行任务的调度切换,出了临界区才会进行任务调度,抢占式
}/********任务1的任务函数,无返回值且是死循环***********//***任务1:实现LED0每500ms翻转一次*******/
void task1(void* args)
{uint8_t task1_num=0;while(1){if(++task1_num==5){task1_num=0;printf("关中断!\n");portDISABLE_INTERRUPTS(); //将BASEPRI寄存器设置为5My_Delay_s(5); // 这里不能调用vTaskDelay(5000); 因为:这个函数内部调用的是临界区保护,将将BASEPRI寄存器设置为0,也就是打开中断,关了又开无法看到实验效果printf("开中断!!!\n");portENABLE_INTERRUPTS(); //将BASEPRI寄存器设置为0,打开中断}vTaskDelay(1000); //FreeRTOS自带的延时函数,延时1秒}}//FreeRTO入口例程函数,无参数,无返回值
void freertos_demo(void)
{xTaskCreate( (TaskFunction_t) start_task,(char *) "start_task", ( configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,(void *) NULL,(UBaseType_t) START_TASK_PRIO ,(TaskHandle_t *) &start_task_handler );vTaskStartScheduler(); //开启任务调度器}
主函数调用入口函数,操作系统开始进行任务的切换和调度
#include "stm32f4xx.h" // Device header
#include "stdio.h"
#include "myled.h"
#include "mykey.h"
#include "myusart.h"
#include "mytim.h"#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"extern TaskHandle_t Start_Handle;int main(void)
{//硬件初始化My_UsartInit();LED_Init();KEY_Init();Interrupt_Init();//调用入口函数freertos_demo();}
本实验创建了两个定时器中断,每隔1秒打印字符串,并且一个定时器中断的优先级在FreeRTOS的管理范围之内,另一个不在FreeRTOS的管理范围之内,这样就可以验证中断管理函数的作用!一开始,两个定时器每隔1秒打印相应的字符串,各自运行5次后,关闭中断,那么定时器6中断就会被屏蔽,因此它不会打印,但定时器7不会受影响,定时器7打印5次后,再打开中断,这时那么定时器6中断就会被打开,两个定时器每隔1秒打印相应的字符串,依次循环往复运行!
至此,中断管理就已经讲解完毕!初次学习,循序渐进,一步步掌握即可!以上就是全部内容!请务必掌握,创作不易,欢迎大家点赞加关注评论,您的支持是我前进最大的动力!下期再见!