FreeRTOS—中断管理

发布于:2025-07-18 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、中断

1.1.中断的简介

中断就是让 CPU 打断正常运行的程序,转而去处理紧急的事件;中断执行机制可分为简单的三步:

  1. 中断请求:外设产生中断(GPIO 外部中断、定时器中断等等)
  2. 响应中断:CPU 停止执行当前程序,转而去执行中断处理程序( ISR )
  3. 执行完毕,返回被打断的程序处,继续往下执行

1.2.中断优先级分组设置

ARM Cortex-M 使用了 8 位宽的寄存器来配置中断的优先等级,这个寄存器就是中断优先等级配置寄存器,但 STM32 只用了中断优先级配置寄存器的高 4 位( Bit4 - Bit7 ),所以 STM32 提供了最大 16 级的中断优先等级。

在这里插入图片描述

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

  • 抢占优先级:抢占优先级高的中断可以打断正在执行但是抢占优先级低的中断
  • 子优先级:当同时发生具体相同抢占优先级的两个中断时,子优先级数值小的优先执行

中断优先级分组一共有五组,可以通过调用HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_x),其中中断优先级数值越小,越优先执行。

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

如果要调用 FreeRTOS 的 API 函数里使用中断,就要注意:

  • 低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断里才允许调用 FreeRTOS的 API 函数
  • 建议将所有优先级位指定为抢占优先级位,方便 FreeRTOS 管理(调用函数HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)
  • 中断优先级数值越小越优先,任务优先级数值越大越优先

1.3.中断相关寄存器

1.3.1.三个系统中断优先级配置寄存器

这三个寄存器分别为:

  • SHPR1 寄存器地址:0xE000ED18
  • SHPR2 寄存器地址:0xE000ED1C
  • SHPR3 寄存器地址:0xE000ED20

一个寄存器占 4 个地址,一个地址就有 8 个位,这样就组成了一个有 32 位的寄存器。如下图中,SysTick 用于单片机的心跳节拍,PendSV 用于控制任务切换或任务调度等。如果想操作 PendSV 的优先级,就把 SHPR3 寄存器的地址偏移 16 位;控制 SysTick 的优先级就偏移 24 位。

在这里插入图片描述

1.3.2.配置 PendSV 和 SysTick 中断优先级

通过下图,寄存器内部的操作,实现了 PendSV 和 SysTick 设置在最低级,设置最低级保证了系统任务切换不会阻塞系统其他中断的响应。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

偏移 8 - 4 位,意思是低 4 位无效。

在这里插入图片描述

1.3.3.三个中断屏蔽寄存器

这三个寄存器分别为:

  • PRIMASK:这是个只有 1 位的寄存器,当它置 1 时,就关闭所有可屏蔽的异常,只剩下 NMI 和 fault 可以响应;置为 0 表示没有关闭中断
  • FAULTMASK:这是个只有 1 位的寄存器,当它置 1 时,只有 NMI 才能响应,所有其他的异常包括中断和 fault 都关闭;置为 0 表示没有关闭中断
  • BASEPRI:这个寄存器最多有 9 位(由表达优先级的位数决定),它定义了被屏蔽优先级的阈值,当它被设置成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优先级越低);置为 0 表示没有关闭中断

FreeRTOS 所使用的中断管理就是利用 BASEPRI 这个寄存器,BASEPRI 就是屏蔽优先级低于设定阈值的中断。例如:BASEPRI 设置为 0x50,代表中断优先级在 5 ~ 15 内的均被屏蔽,0 ~ 4 的中断优先级正常执行。

二、实验

2.1.实验设计

设计两个定时器,一个优先级为 4,一个优先级为 6(系统所管理的优先级范围:5 ~ 15);两个定时器的现象:每 1s 打印一段字符串,当关中断时,停止打印,开中断时持续打印。本实验将设置两个任务:

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

2.2.软件设计

在定时器 TIMER 文件夹里的 btim.c 文件,初始化定时器 6 和定时器 7 ,其中定时器 6 的抢占优先级设置为 6,定时器 7 的抢占优先级设置为 4。
下面是 btim.c 代码:

#include "./BSP/LED/led.h"
#include "./BSP/TIMER/btim.h"
#include "./SYSTEM/usart/usart.h"

TIM_HandleTypeDef g_tim6_handle;    
TIM_HandleTypeDef g_tim7_handle;     

//初始化定时器6
void btim_tim6_int_init(uint16_t arr, uint16_t psc)
{
    BTIM_TIMX_INT_CLK_ENABLE();                                    
    
    g_tim6_handle.Instance = BTIM_TIM6_INT;                        
    g_tim6_handle.Init.Prescaler = psc;                           
    g_tim6_handle.Init.CounterMode = TIM_COUNTERMODE_UP;             
    g_tim6_handle.Init.Period = arr;                                
    g_tim6_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;      
    HAL_TIM_Base_Init(&g_timx_handle);
    
    HAL_NVIC_SetPriority(BTIM_TIM6_INT_IRQn, 6, 0);
    HAL_NVIC_EnableIRQ(BTIM_TIM6_INT_IRQn);         
    HAL_TIM_Base_Start_IT(&g_tim6_handle);          
}

//初始化定时器7
void btim_tim7_int_init(uint16_t arr, uint16_t psc)
{
    BTIM_TIM7_INT_CLK_ENABLE();                                      
    
    g_tim7_handle.Instance = BTIM_TIM7_INT;
    g_tim7_handle.Init.Prescaler = psc;                          
    g_tim7_handle.Init.CounterMode = TIM_COUNTERMODE_UP;           
    g_tim7_handle.Init.Period = arr;                                
    g_tim7_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;      
    HAL_TIM_Base_Init(&g_tim7_handle);
    
    HAL_NVIC_SetPriority(TIM7_IRQn, 4, 0); 
    HAL_NVIC_EnableIRQ(TIM7_IRQn);       
    HAL_TIM_Base_Start_IT(&g_tim7_handle);         
}

//定时器6的中断服务函数
void BTIM_TIM6_INT_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_tim6_handle);
}

//定时器7的中断服务函数
void BTIM_TIM7_INT_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_tim7_handle);
}

//定时器更新中断回调函数,参数是定时器句柄指针
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&g_tim6_handle))
    {
		printf("666\r\n");
    }else if(htim == (&g_tim7_handle))
	{
		printf("7777\r\n");
	}
}

下面是 btim.h 代码:

#ifndef __BTIM_H
#define __BTIM_H

#include "./SYSTEM/sys/sys.h"
 
#define BTIM_TIM6_INT                       TIM6
#define BTIM_TIM6_INT_IRQn                  TIM6_DAC_IRQn
#define BTIM_TIM6_INT_IRQHandler            TIM6_DAC_IRQHandler
#define BTIM_TIM6_INT_CLK_ENABLE()          do{ __HAL_RCC_TIM6_CLK_ENABLE(); }while(0)  

#define BTIM_TIM7_INT                       TIM7
#define BTIM_TIM7_INT_IRQn                  TIM7_IRQn
#define BTIM_TIM7_INT_IRQHandler            TIM7_IRQHandler
#define BTIM_TIM7_INT_CLK_ENABLE()          do{ __HAL_RCC_TIM7_CLK_ENABLE(); }while(0) 

void btim_tim6_int_init(uint16_t arr, uint16_t psc);
void btim_tim7_int_init(uint16_t arr, uint16_t psc);

#endif

下面是 freertos_demo.c 代码:

#include "freertos_demo.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"


/*FreeRTOS*********************************************************************************************/

#include "FreeRTOS.h"
#include "task.h"

/******************************************************************************************************/

#define START_TASK_PRIO           1
#define START_TASK_STACK_SIZE     128
TaskHandle_t  start_task_handler;
void start_task(void *pvParameters);


#define TASK1_PRIO           2
#define TASK1_STACK_SIZE     128
TaskHandle_t  task1_handler;
void task1(void *pvParameters);

/******************************************************************************************************/
//入口函数
void freertos_demo(void)
{
    xTaskCreate((TaskFunction_t)    start_task,
                (char*)             "start_task",
                (uint16_t)          START_TASK_STACK_SIZE,
                (void*)             NULL,
                (UBaseType_t)       START_TASK_PRIO,
                (TaskHandle_t*)     &start_task_handler);
    vTaskStartScheduler();  		
}

//start_task
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();   
	
	xTaskCreate((TaskFunction_t)    task1,	
                (char*)             "task1",
                (uint16_t)          TASK1_STACK_SIZE,
                (void*)             NULL,
                (UBaseType_t)       TASK1_PRIO,
                (TaskHandle_t*)     &task1_handler);

	vTaskDelete(NULL);	
    taskEXIT_CRITICAL();            
}

//task1,num每到5就关一次中断,隔5秒就开中断
void task1(void *pvParameters)
{
	uint32_t num = 0;
	while(1)
	{
		if(++num == 5)
		{
			num = 0;
			printf("关中断!!\r\n");
			portDISABLE_INTERRUPTS();
			delay_ms(5000);		//为什么这里不使用FreeRTOS的延迟函数呢?
								//因为FreeRTOS的延迟函数里面会调用使能中断的函数,也就是下一句的函数,相当于你关了中断,又立刻打开中断,这样实验就没意义了
			printf("开中断\r\n");
			portENABLE_INTERRUPTS();
		}
		vTaskDelay(1000);
	}
}

最后的实验现象如下图:

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到