3.6 FreeRTOS中断管理

中断知识回顾

注意:中断优先级数值越小越优先

我们之前学习过裸机,应该很明白中断的概念,这里再复习一下相关知识,让CPU打断正常运行的程序,转而去处理紧急的事件(程序),就叫中断 。

ARM Cortex-M 使用了 8 位宽的寄存器来配置中断的优先等级,这个寄存器就是中断优先级配置寄存器。但STM32,只用了中断优先级配置寄存器的高4位 [7 : 4],即bit4~bit7这四位,所以STM32提供了最大16级的中断优先等级。

STM32 的中断优先级可以分为抢占优先级和子优先级:

抢占优先级: 抢占优先级高的中断可以打断正在执行但抢占优先级低的中断。

子优先级:当同时发生具有相同抢占优先级的两个中断时,子优先级数值小的优先执行。

FreeRTOS为了方便管理,分组方式要选择NVIC_PriorityGroup_4:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

FreeRTOS中断管理

FreeRTOS中断特点:

1、低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断里才允许调用FreeRTOS 的API函数。

2、建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理。

3、中断优先级数值越小越优先,任务优先级数值越大越优先。

系统中断优先级配置

三个系统中断优先级配置寄存器,分别为 SHPR1、 SHPR2、 SHPR3。

这个 SHPR3(0xE000ED20 )寄存器,可以配置滴答定时器、PendSV的优先级,滴答定时器是为我们系统提供心跳节拍,而PendSV是任务切换、任务调度都是在PendSV中实现的。

在FreeRTOS中是如何配置PendSV和Systick中断优先级的?

其实没有什么内容,只是宏定义比较多,就是把寄存器的bit4~bit7给填充上,让这些有了优先级,Systick和PendSV的中断优先级都是15,就是最低的中断优先级。

为什么要设置成最低优先级呢?

是为了保证系统任务切换不会阻塞系统其他中断的响应。就是我中断可以打断你任务,但是你任务无法打断我中断。

中断屏蔽寄存器

三个中断屏蔽寄存器,分别为 PRIMASK、 FAULTMASK 和BASEPRI。

我们的FreeRTOS就是利用BASEPRI寄存器来管理中断的,就是中断优先级小于我们这里设置的阈值,就会被关闭。

比如: BASEPRI设置为0x50,代表中断优先级在5~15内的均被屏蔽,0~4的中断优先级正常执行。

可以看出,我们的FreeRTOS相当于接管了5~15的中断,让原本的5~15被屏蔽。

在中断服务函数中调度FreeRTOS的API函数需注意:

1、中断服务函数的优先级需在FreeRTOS所管理的范围内。

2、在中断服务函数里边需调用FreeRTOS的API函数,必须使用带“FromISR”后缀的函数。

中断管理实验

实验目的:学会使用FreeRTOS的中断管理。

本实验会使用两个定时器,一个优先级为4,一个优先级为6,注意:系统所管理的优先级范围:5~15。所以中断优先级为4的定时器,理论上FreeRTOS是无法进行控制的,所以我们来测试一下,到底能不能控制。(提前透露答案:不能)

目标设计:

将设计2个任务:start_task、task1。

start_task:用来创建task1任务 。

task1:中断测试任务,任务中将调用关中断和开中断函数来体现对中断的管理作用。

这一节课我们需要用到定时器,所以我们先把定时器的初始化代码写一下:

定时器/中断初始化

void Timer_Init(void)
{
	//开启通用定时器TIM2、TIM3的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	
	//选择时基单元的时钟是内部时钟(168MHz)
	TIM_InternalClockConfig(TIM2);
	TIM_InternalClockConfig(TIM3);
	
	//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStrcture;
	TIM_TimeBaseInitStrcture.TIM_ClockDivision=TIM_CKD_DIV1; //指定采样分频。
	TIM_TimeBaseInitStrcture.TIM_CounterMode=TIM_CounterMode_Up;//指定计数器模式
	TIM_TimeBaseInitStrcture.TIM_Period=10000-1;//自动重装寄存器ARR的值
	TIM_TimeBaseInitStrcture.TIM_Prescaler=16800-1;//预分频器PSC的值
	TIM_TimeBaseInitStrcture.TIM_RepetitionCounter=0;//通用定时器没有重复计数器,填0
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStrcture);
	
	TIM_TimeBaseInitStrcture.TIM_ClockDivision=TIM_CKD_DIV1; //指定采样分频。
	TIM_TimeBaseInitStrcture.TIM_CounterMode=TIM_CounterMode_Up;//指定计数器模式
	TIM_TimeBaseInitStrcture.TIM_Period=10000-1;//自动重装寄存器ARR的值
	TIM_TimeBaseInitStrcture.TIM_Prescaler=16800-1;//预分频器PSC的值
	TIM_TimeBaseInitStrcture.TIM_RepetitionCounter=0;//通用定时器没有重复计数器,填0
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStrcture);
	
	// 清除更新中断标志位
  TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	
	//配置输出中断控制
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
	
	//配置NVIC
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;//NVIC通道
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能该中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=4;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;//NVIC通道
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能该中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_InitStructure);
	
	//启动定时器
	TIM_Cmd(TIM2,ENABLE);
	TIM_Cmd(TIM3,ENABLE);
}


void TIM2_IRQHandler(){
	if (TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
	{
		printf("优先级4,TIM2,正在运行!!\r\n");
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

void TIM3_IRQHandler(){
	if (TIM_GetITStatus(TIM3,TIM_IT_Update) == SET)
	{
		printf("优先级6,TIM3,正在运行!!\r\n");
		TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
	}
}

 

有了上面的初始化代码,我们的中断目前来讲,是都可以在串口每隔1s进行一次打印的,接下来我们来实现FreeRTOS的任务函数,在FreeRTOS中我们要实现开关中断的操作来证明FreeRTOS是可以控制中断的,所以我们要认识一个新的FreeRTOS的API函数:

portDISABLE_INTERRUPTS(); //关闭中断函数
portENABLE_INTERRUPTS(); //开启中断函数
void task1(void *pvParameters)
{
	uint8_t task1_num = 0;

	while(1)
	{
		task1_num++;
		if(task1_num == 5)
		{
			task1_num = 0;
			 printf("关闭中断\r\n");
			 portDISABLE_INTERRUPTS();
			delay_ms(5000);
		   printf("开启中断\r\n");
			portENABLE_INTERRUPTS();
		}
		LED0_turn();
		vTaskDelay(1000);//使用FreeRTOS自带的延时函数
	}
}

注意,我们这里使用的是delay_ms而不是vTaskDelay,这是为什么呢?

因为vTaskDelay的实现有问题,它里面调用了临界区,临界区中的退出临界区的函数会调用到portENABLE_INTERRUPTS();

即开中断,就是vTaskDelay函数会变相的开启中断,所以不能用vTaskDelay。