本章将向大家介绍 FreeRTOS 的中断管理,通过本章的学习,让大家对 FreeRTOS 中断管理更加了解,为后面嵌入式系统开发做好铺垫。本章要实现的 功能是:创建了两个任务,分别是 LED 任务与中断管理任务。使用两个定时器, 一个优先级为 4,一个优先级为 5,两个定时器每隔 1s 通过串口输出一串字符 串。然后在中断管理任务中关闭中断一段时间,查看两个定时器的输出情况;LED 任务是将 LED 翻转,表示系统处于运行状态。
1. 临界段保护简介
临界段用一句话概括就是一段在执行的时候不能被中断的代码段。在 FreeRTOS 里面,这个临界段最常出现的就是对全局变量的操作。 那么什么情况下临界段会被打断?一个是系统调度,还有一个就是外部中 断。在 FreeRTOS,系统调度,最终也是产生 PendSV 中断,在 PendSV Handler 里面实现任务的切换,所以还是可以归结为中断。既然这样,FreeRTOS 对临界 段的保护最终还是回到对中断的开和关的控制。
1.1 Cortex-M 内核快速开关中断指令
为了快速地开关中断,Cortex-M 内核专门设置了一条 CPS 指令,有 4 种用 法,具体如下
CPSID I ;PRIMASK=1 ;关中断
CPSIE I ;PRIMASK=0 ;开中断
CPSID F ;FAULTMASK=1 ;关异常
CPSIE F ;FAULTMASK=0 ;开异
PRIMASK 和 FAULTMAST 是 Cortex-M 内核里面三个中断屏蔽寄存器中的两 个,还有一个是 BASEPRI,有关这三个寄存器的详细用法如下
![](https://img-blog.csdnimg.cn/img_convert/2555cf75960d90795c47ccbe7eb4d68f.png)
但是,在 FreeRTOS 中,对中断的开和关是通过操作 BASEPRI 寄存器来实 现的,即大于等于 BASEPRI 的值的中断会被屏蔽,小于 BASEPRI 的值的中断则 不会被屏蔽,不受 FreeRTOS 管理。用户可以设置 BASEPRI 的值来选择性的给 一些非常紧急的中断留一条后路
1.1 临界段代码
临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段, 比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS 在 进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。 FreeRTOS 系统本身就有很多的临界段代码,这些代码都加了临界段代码保护, 我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。 FreeRTOS 与临界段代码保护有关的函数有 4 个,在 task.h 文件中有定义。 如下:
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
这四个函数的区别是前两个是任务级的临界段代码保护,后两个是中断级的 临界段代码保护
2. 中断管理简介
ARM Cortex-M 系列内核的中断是由硬件管理的,而 FreeRTOS 是软件,它 并不接管由硬件管理的相关中断(接管简单来说就是,所有的中断都由 RTOS 的 软件管理,硬件来了中断时,由软件决定是否响应,可以挂起中断,延迟响应或 者不响应),只支持简单的开关中断等,所以 FreeRTOS 中的中断使用其实跟裸 机差不多的,需要我们自己配置中断,并且使能中断,编写中断服务函数,在中 断服务函数中使用内核 IPC 通信机制,一般建议使用信号量、消息或事件标志 组等标志事件的发生,将事件发布给处理任务,等退出中断后再由相关处理任务 具体处理中断。 用户可以自定义配置系统可管理的最高中断优先级的宏定义 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY,它是用于配置内核中的 basepri 寄存器的,当 basepri 设置为某个值的时候,NVIC 不会响应比该优先 级低的中断,而优先级比之更高的中断则不受影响。就是说当这个宏定义配置为 5 的时候,中断优先级数值在 0、1、2、3、4 的这些中断是不受 FreeRTOS 屏 蔽的,也就是说即使在系统进入临界段的时候,这些中断也能被触发而不是等到 退出临界段的时候才被触发,当然,这些中断服务函数中也不能调用 FreeRTOS 提供的 API 函数接口,而中断优先级在 5 到 15 的这些中断是可以被屏蔽的, 也能安全调用 FreeRTOS 提供的 API 函数接口。 ARM Cortex-M NVIC 支持中断嵌套功能:当一个中断触发并且系统进行响应 时,处理器硬件会将当前运行的部分上下文寄存器自动压入中断栈中,这部分的 寄存器包括 PSR,R0,R1,R2,R3 以及 R12 寄存器。当系统正在服务一个中断 时,如果有一个更高优先级的中断触发,那么处理器同样的会打断当前运行的中 断服务例程,然后把老的中断服务例程上下文的 PSR,R0,R1,R2,R3 和 R12 寄 存器自动保存到中断栈中。这些部分上下文寄存器保存到中断栈的行为完全是硬 件行为,这一点是与其他 ARM 处理器最大的区别(以往都需要依赖于软件保存 上下文)。 另外,在 ARM Cortex-M 系列处理器上,所有中断都采用中断向量表的方式 进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳 转到相应的固定位置进行处理。而在 ARM7、ARM9 中,一般是先跳转进入 IRQ 入 口,然后再由软件进行判断是哪个中断源触发,获得了相对应的中断服务例程入 口地址后,再进行后续的中断处理。ARM7、ARM9 的好处在于,所有中断它们都 有统一的入口地址,便于 OS 的统一管理。而 ARM Cortex-M 系列处理器则恰恰 相反,每个中断服务例程必须排列在一起放在统一的地址上(这个地址必须要设 置到 NVIC 的中断向量偏移寄存器中)。中断向量表一般由一个数组定义(或在 起始代码中给出),在 STM32 上,默认采用启动文件代码给出
3.整体代码
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "time.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define Interrupt_TASK_PRIO 4
//任务堆栈大小
#define Interrupt_STK_SIZE 512
//任务句柄
TaskHandle_t InterruptTask_Handler;
//任务函数
void Interrupt_task(void *pvParameters);
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
KEY_Init();
USART1_Init(115200);
TIM3_Init(10000-1,7200-1);//定时1S
TIM4_Init(10000-1,7200-1);
printf("FreeRTOS中断管理实验\r\n");
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建Interrupt任务
xTaskCreate((TaskFunction_t )Interrupt_task,
(const char* )"Interrupt_task",
(uint16_t )Interrupt_STK_SIZE,
(void* )NULL,
(UBaseType_t )Interrupt_TASK_PRIO,
(TaskHandle_t* )&InterruptTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//Interrupt任务函数
void Interrupt_task(void *pvParameters)
{
static u32 total_num=0;
while(1)
{
total_num+=1;
if(total_num==5)
{
printf("关闭中断.............\r\n");
portDISABLE_INTERRUPTS(); //关闭中断
delay_xms(5000); //延时5s,使用不影响任务调度的延时
printf("打开中断.............\r\n");
portENABLE_INTERRUPTS();//打开中断
}
LED2=!LED2;
vTaskDelay(1000);
}
}