首页 > 其他分享 >FreeRTOS从入门到精通 第五章(中断管理)

FreeRTOS从入门到精通 第五章(中断管理)

时间:2025-01-01 13:00:56浏览次数:3  
标签:TIM2 TIM3 优先级 入门 FreeRTOS 中断 NVIC TIM 第五章

参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili

一、中断的回顾与深入

1、概述

(1)让CPU打断正常运行的程序,转而去处理紧急的事件(程序),这个过程就叫中断,受理的事件称为中断服务程序。

(2)中断执行机制,可简单概括为三步:

①外设产生中断请求(GPIO外部中断、定时器中断等)。

②CPU停止执行当前程序,转而去执行中断处理程序(ISR)。

③执行完毕,返回被打断的程序处,继续往下执行。

(3)中断服务函数的优先级需在FreeRTOS所管理的范围内;在中断服务函数里面若需调用FreeRTOS的API函数,只能使用带“FromISR”后缀的函数。

2、中断优先级及分组

(1)ARM Cortex-M使用了8位宽的寄存器来配置中断的优先等级,这个寄存器就是中断优先级配置寄存器,但STM32只用了中断优先级配置寄存器的高4位[7 : 4],所以STM32提供了最大16级的中断优先等级(中断优先级数值越小,等级越高)。

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

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

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

(3)中断优先级分组一共有5种分配方式,对应着中断优先级分组的5个组,通过调用函数NVIC_PriorityGroupConfig(NVIC_PriorityGroup_x)可完成设置,建议将所有优先级位指定为抢占优先级位(即NVIC_PriorityGroup_4),方便FreeRTOS管理。

优先级分组 

抢占优先级 

子优先级 

优先级配置寄存器高 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 用于子优先级

(4)移植了FreeRTOS的工程中,在低于configMAX_SYSCALL_INTERRUPT_PRIORITY(FreeRTOS可管理的最高中断优先级)优先级的中断里才允许调用FreeRTOS的API函数。

3、中断相关寄存器

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

①SHPR1寄存器的地址为0xE000ED18,SHPR2寄存器的地址为0xE000ED1C,SHPR3寄存器的地址为0xE000ED20。

②PendSV和SysTick中断设置最低优先级,这是为了保证系统任务切换不会阻塞系统其它中断的响应。

(2)STM32F103C8T6中有三个中断屏蔽寄存器,分别为PRIMASK、FAULTMASK和BASEPRI,FreeRTOS所使用的中断管理就是利用BASEPRI这个寄存器,它可屏蔽优先级低于某一个阈值的中断(当设置为0时,不关闭任何中断)。

①当BASEPRI设置为0x50时,中断优先级在5 ~ 15的中断全部被关闭。

#define portDISABLE_INTERRUPTS() 		vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void ) 
{ 
	uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; 
	__asm 
	{
		msr basepri, ulNewBASEPRI   //将ulNewBASEPRI写入寄存器BASEPRI
		dsb 
		isb
	} 
}
#define configMAX_SYSCALL_INTERRUPT_PRIORITY    (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) 
//使用BASEPRI的高4位,故需左移(8 - configPRIO_BITS)位
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY   5   
//FreeRTOS可管理的最高中断优先级

②当BASEPRI设置为0x00时,不关闭任何中断。

#define portENABLE_INTERRUPTS()		 vPortSetBASEPRI( 0 )  //参数为0
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI ) 
{ 
	__asm
	{
		msr basepri, ulBASEPRI   //将ulNewBASEPRI写入寄存器BASEPRI
	} 
}

二、FreeRTOS中断管理实验

1、原理图与实验目标

(1)原理图:

(2)实验目标:

①使用两个定时器TIM2和TIM3,TIM2中断的优先级为4,相应中断服务程序为控制LED1状态翻转,TIM3中断的优先级为6,相应中断服务程序为控制LED2状态翻转,系统所管理的优先级范围为5~15。

②设计2个任务——start_task、task3:

[1]start_task:用于创建task3任务。

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

③预期实验现象:

[1]程序下载到板子上后,两个LED灯闪烁。

[2]按下按键1,LED2停止闪烁,LED1仍继续闪烁。

[3]按下按键2,LED2恢复闪烁。

2、实验步骤

(1)将“任务创建和删除的动态方法实验”的工程文件夹复制一份,在拷贝版中进行实验。

(2)在stm32教程中找到“定时器定时中断”实验,复制其中的Timer.c文件和Timer.h文件,将其添加到本实验的项目中,如下图所示。

(3)将key.c文件以及FreeRTOS_experiment.c文件中的阻塞函数更改为Delay_ms死延时函数,因为vTaskDelay函数的底层中调用了开中断函数,不利于实验现象的观察。

(4)在Timer.c文件中完成TIM2和TIM3初始化函数的实现,并在Timer.h文件中声明。

#include "stm32f10x.h"                  // Device header

void Timer2_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);	  //开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;			//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;			//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;		//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);		//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);		//清除定时器更新标志位
	//TIM_TimeBaseInit函数末尾,手动产生了更新事件
	//若不清除此标志位,则开启中断后,会立刻进入一次中断
	//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);		//开启TIM2的更新中断
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;				//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;	//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4;	//指定NVIC线路的抢占优先级为4
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		//指定NVIC线路的响应优先级为0
	NVIC_Init(&NVIC_InitStructure);	//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

void Timer3_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	  //开启TIM3的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;			//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;			//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;		//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);		//将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);	//清除定时器更新标志位
	//TIM_TimeBaseInit函数末尾,手动产生了更新事件
	//若不清除此标志位,则开启中断后,会立刻进入一次中断
	//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);		//开启TIM3的更新中断
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;				//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;	//选择配置NVIC的TIM3线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;	//指定NVIC线路的抢占优先级为6
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		//指定NVIC线路的响应优先级为0
	NVIC_Init(&NVIC_InitStructure);	//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}

(5)将FreeRTOSConfig.h文件中的configMAX_SYSCALL_INTERRUPT_PRIORITY定义为(5 << 4),主要是为了配置FreeRTOS可管理的最高中断优先级为5,这个值直接写入BASEPRI寄存器中。

#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	(5 << 4)

(6)删除FreeRTOS_experiment.c文件中关于task1和task2的内容,并更改task3函数的具体实现,按下按键1,FreeRTOS关中断,按下按键2,FreeRTOS开中断。

#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"
#include "Key.h"
#include "Delay.h"

//宏定义
#define START_TASK_STACK_SIZE 128   //start_task任务的堆栈大小
#define START_TASK_PRIO       1     //start_task任务的优先级
#define TASK3_STACK_SIZE      128   //task3任务的堆栈大小
#define TASK3_PRIO            4     //task3任务的优先级

//任务函数声明
void start_task(void);
void task3(void);

//任务句柄
TaskHandle_t start_task_handler;    //start_task任务的句柄
TaskHandle_t task3_handler;         //task3任务的句柄


void FreeRTOS_Test(void)
{
	//创建任务start_task
	xTaskCreate((TaskFunction_t)start_task,            //指向任务函数的指针
				"start_task",                       //任务名字
				START_TASK_STACK_SIZE,       //任务堆栈大小,单位为字
				NULL,                           //传递给任务函数的参数
				START_TASK_PRIO,              //任务优先级
				(TaskHandle_t *) &start_task_handler  //任务句柄,就是任务的任务控制块
				);
	
	//开启任务调度器
	vTaskStartScheduler();
}


void start_task(void)
{
	//进入临界区(临界区保护,就是保护那些不想被打断的程序段)
	taskENTER_CRITICAL();
	
	//创建任务task3
	xTaskCreate((TaskFunction_t)task3,             //指向任务函数的指针
				"task3",                        //任务名字
				TASK3_STACK_SIZE,           //任务堆栈大小,单位为字
				NULL,                        //传递给任务函数的参数
				TASK3_PRIO,                  //任务优先级
				(TaskHandle_t *) &task3_handler   //任务句柄,就是任务的任务控制块
				);
	
	//删除任务自身
	vTaskDelete(NULL);
	
	//退出临界区
	taskEXIT_CRITICAL();
}

void task3(void)
{
	uint8_t key = 0;
	while(1)
	{
		key = Key_GetNum();    //读取按键键值
		if(key == 1)
		{
			portDISABLE_INTERRUPTS();    //关中断,中断优先级在5~15的中断全部被关闭
		}
		if(key == 2)
		{
			portENABLE_INTERRUPTS();     //开中断
		}
		Delay_ms(10);  //延时10ms,这里不能使用自我阻塞,其底层会开中断,导致看不到预期效果
	}
}

(7)main.c文件的主函数中需要配置NVIC中断优先级分组,然后初始化两个定时器(先分组,再配置,顺序不能反),并在主函数外实现两个定时器分别对应的中断服务程序,TIM2中断服务程序控制LED1状态翻转,TIM3中断服务程序控制LED2状态翻转。

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "FreeRTOS.h"
#include "task.h"
#include "FreeRTOS_experiment.h"
#include "Key.h"
#include "LED.h"
#include "Timer.h"

int main(void)
{
	/*模块初始化*/
	Key_Init();         //Key初始化
	LED_Init();         //LED初始化
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);	//配置NVIC为分组4
	                    //即抢占优先级范围:0~15,响应优先级范围:0
	Timer2_Init();      //定时器2中断初始化
	Timer3_Init();      //定时器3中断初始化
	
	FreeRTOS_Test();
	while (1)
	{
		
	}
}

void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		LED1_Turn();      //LED1状态翻转
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

void TIM3_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
	{
		LED2_Turn();      //LED2状态翻转
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	}
}

(8)程序完善好后点击“编译”,然后将程序下载到开发板上。

3、程序执行流程

(1)main函数全流程:

①初始化按键模块、LED模块,配置NVIC中断优先级分组,初始化定时器TIM2和TIM3。

②调用FreeRTOS_Test函数(此处未画出中断可能将main函数打断)。

(2)测试函数全流程(此处未画出中断可能将测试函数打断):

①创建任务start_task。

②开启任务调度器。

(3)多任务调度执行阶段(发生在开启任务调度器以后):

①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断,接着start_task任务创建任务task3,然后删除自身,接着退出临界区,让出CPU资源。(此处未画出中断可能将start_task打断)

②start_task任务自我删除后,除了空闲任务以外,只剩下task3任务需要执行。

③按下按键1,中断优先级数值大于等于5的中断都被FreeRTOS关闭,即TIM2中断被关闭,但TIM3中断仍为打开状态。

④按下按键2,FreeRTOS打开所有能管理的中断,即TIM2中断和TIM3中断均为打开状态。

标签:TIM2,TIM3,优先级,入门,FreeRTOS,中断,NVIC,TIM,第五章
From: https://blog.csdn.net/Zevalin/article/details/144865877

相关文章

  • Java Agent(二)、Javassist入门
    目录1、前言2、什么是Javassist?2.1、Javassist的特点2.2、应用场景3、快速开始3.1、maven依赖3.2、生成一个class文件3.2.1、具体代码3.2.2、执行结果3.3、修改已有类的方法3.3.1、具体代码3.3.2、执行结果3.3.3、问题踩坑3.4、修改属性值3.4.1、具体代码3......
  • 《100天学习Python:从入门到精通》——第4天:Python变量的定义及使用
    大家好啊,今天我就来和大家分享一下关于变量的定义及使用吧。1.Python变量的定义及初始化Python变量名要求:1.变量名只能由字母、下划线、数字组成,不能是别的符号。2.变量名开头只能是字母和下划线,不能是数字。3.尽量不要与Python标准库里的函数或第三方模块中的函数重名。......
  • 【优选算法 & 分治】深入理解分治算法:分治算法入门小专题详解
             快速排序算法   (1)快速排序法       (2) 快排前后指针     (3)快排挖坑法   颜色分类  题目解析    算法原理   算法原理和移动零非常相似  简述移动零的算法原理   ......
  • spring cloud-nacos注册中心入门指南
    注册中心为什么引入注册中心?引入注册中心的主要原因是为了解决微服务架构中服务发现和动态管理的问题。在微服务架构中,服务提供者和消费者之间需要进行远程调用。由于微服务数量众多且动态变化,手动维护服务地址列表不仅效率低下,而且容易出错。注册中心的引入解决了这些问......
  • 分布式事务-Seata入门指南
    Seata入门指南为什么要使用分布式事务问题点-当账户余额0时,还是可以下单成功,而且扣减库存新的需求-下单逻辑需要保证数据一致性,当帐户余额不够时,库存回滚,下单失败解决方案采用spring事务能解决问题上面的问题嘛,是不能使用分布式事务解决方案Seata(官方推荐)Seata是......
  • 01NLP开始入门
    WEB项目看来是完不成了,先把论文做了吧,我干嘛选NLP。。。一、经典的NLP和现在的NLP经典的NLP经典的NLP需要学习词袋模型,TFIDF,wordtovector,LSTM,LDA,很多都是传统算法和传统模型。这些东西完全不用学习了,完全用不上。现在的NLP是一个纯的Transformer的模型拼的是预选模......
  • Linux入门基础(Centos7)
    Linux入门基础Linux内核Linux的目录结构Linux的层级关系表示Linux命令基础格式ls命令cd命令pwd命令相对路径和绝对路径特殊路径符mkdir命令touch命令cat命令more命令cp命令mv命令rm命令which命令find命令grep命令wc命令管道符echo命令反引号重定向符tail命令vim编辑器命......
  • Python入门教程 —— 安装软件
    1.安装python下载python访间Python官网:https:/www.python.org  安装python自定义安装,配置Python的环境变量。 选着安装路径,然后安装安装成功后,查看python版本:python-V2.安装 pycharm选着安装路径......
  • 关于C++游戏开发入门:如何从零开始实现一个完整的游戏项目!
      成长路上不孤单......
  • MongoDB 入门操作(Introduction to MongoDB Operations)
    MongoDB入门操作目录MongoDB是非关系型文档数据库的典型代表,非常适合写入大批量、高并发以及不规则的数据,因此广泛应用于爬虫开发的存储后端。✍当业务需求对联表查询不是很强烈,相反需要存储大量关联性不是很强的数据时,可以考虑使用MongoDB文档型数据库。在Mon......