参考教程:【正点原子】手把手教你学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 用于抢占优先级 |
NVIC_PriorityGroup_1 | 0-1 级抢占优先级 | 0-7 级子优先级 | 1bit 用于抢占优先级 |
NVIC_PriorityGroup_2 | 0-3 级抢占优先级 | 0-3 级子优先级 | 2bit 用于抢占优先级 |
NVIC_PriorityGroup_3 | 0-7 级抢占优先级 | 0-1 级子优先级 | 3bit 用于抢占优先级 |
NVIC_PriorityGroup_4 | 0-15 级抢占优先级 | 0 级子优先级 | 4bit 用于抢占优先级 |
(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