目录
一、什么是中断
二、中断优先级分组设置
2.1、中断优先级基本概念
2.2、中断优先级分组
2.3、FreeRTOS中断特点
三、中断相关寄存器
3.1、系统中断优先级配置寄存器
3.2、PendSV和Systick中断优先级的配置
3.3、中断屏蔽寄存器
四、FreeRTOS中断管理实验
一、什么是中断
让 CPU 打断正常运行的程序,转而去处理紧急的事件(程序),就叫中断
中断执行机制,可简单概括为三步
1、中断请求:外设产生中断请求(GPIO外部中断、定时器中断等)
2、响应中断:CPU 停止执行当前程序,转而去执行中断处理程序(ISR)
3、退出中断:执行完毕,返回被打断的程序处,继续往下执行
二、中断优先级分组设置
2.1、中断优先级基本概念
ARM Cortex-M 使用了 8 位宽的寄存器来配置中断的优先等级,这个寄存器就是中断优先级配置寄存器,但 STM32,只用了中断优先级配置寄存器的高 4 位 [7:4],所以 STM32 提供了最大16级的中断优先级
STM32 的中断优先级可以分为抢占优先级和子优先级
抢占优先级:高抢占优先级可以打断正在执行的低抢占优先级中断
子优先级:当抢占优先级相同时,子优先级高的先执行,但是不能互相打断
注意:中断优先级数值越小越优先
2.2、中断优先级分组
一共有 5 种分配方式,对应着中断优先级分组的 5 个组
优先级分组 | 抢占优先级 | 子优先级 | 优先级配置寄存器高 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 中使用的是 NVIC_PRIORITYGROUP_4,任何其他配置都会使 configMAX_SYSCALL_INTERRUPT_PRIORITY 设置与分配给各个外设中断的优先级之间的直接关系复杂化
在 HAL_Init 中,通过调用函数 HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4) 即可完成设置
2.3、FreeRTOS中断特点
1、低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 优先级的中断里才允许调用 FreeRTOS 的 API 函数
2、建议将所有优先级位指定为抢占优先级位,方便 FreeRTOS 管理(调用函数 HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4) )
3、中断优先级数值越小越优先,任务优先级数值越大越优先
三、中断相关寄存器
3.1、系统中断优先级配置寄存器
三个系统中断优先级配置寄存器,分别为 SHPR1、SHPR2、SHPR3
SHPR1 寄存器地址:0xE000ED18
SHPR2 寄存器地址:0xE000ED1C
SHPR3 寄存器地址:0xE000ED20
3.2、PendSV和Systick中断优先级的配置
在 xPortStartScheduler() 函数中,将 PendSV 和 SysTick 设置为优先级最低的中断,这个函数在 vTaskStartScheduler() 开启任务调度函数中会调用
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
SHPR3 寄存器地址的定义
#define portNVIC_SHPR3_REG ( *( ( volatile uint32_t * ) 0xe000ed20 ) )
PendSV 和 SysTick 优先级配置寄存器相对 SHPR3 寄存器的偏移
#define portNVIC_PENDSV_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
#define portNVIC_SYSTICK_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )
将设置的中断最低优先级左移 4 位作为中断优先级配置寄存器的高 4 位 [7:4] 进行赋值
/* 中断嵌套行为配置 */
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 /* 中断最低优先级 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /* FreeRTOS可管理的最高中断优先级 */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
偏移 4 的定义
#define __NVIC_PRIO_BITS 4 /*!< STM32F4XX uses 4 Bits for the Priority Levels */
所以:PendSV 和 SysTick 设置为最低优先级
设置最低:保证系统任务切换不会阻塞系统其他中断的响应
3.3、中断屏蔽寄存器
三个中断屏蔽寄存器,分别为 PRIMASK、FAULTMASK、BASEPRI
FreeRTOS 所使用的中断管理就是利用的 BASEPRI 这个寄存器
BASEPRI:屏蔽优先级低于某一个阈值的中断,当设置为 0 时,则不关闭任何中断
比如:BASEPRI 设置为 0x50,代表中断优先级在 5~15 内的均被屏蔽,0~4 的中断优先级正常执行
关中断程序示例(中断优先级在 5~15 的全部被关闭)
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
* section. */
/* *INDENT-OFF* */
msr basepri, ulNewBASEPRI
dsb
isb
/* *INDENT-ON* */
}
}
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /* FreeRTOS可管理的最高中断优先级 */
当 BASEPRI 设置为 0x50 时
在中断服务函数中调度 FreeRTOS 的 API 函数需注意
1、中断服务函数的优先级需在 FreeRTOS 所管理的范围内
2、在中断服务函数里边需调用 FreeRTOS 的 API 函数,必须使用带“FromISR”后缀的函数
开中断程序示例
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* Barrier instructions are not used as this function is only used to
* lower the BASEPRI value. */
/* *INDENT-OFF* */
msr basepri, ulBASEPRI
/* *INDENT-ON* */
}
}
FreeRTOS 中断管理就是利用 BASEPRI 寄存器实现的
四、FreeRTOS中断管理实验
本实验会使用两个定时器每 1s 分别打印一段字符串,一个优先级为 4,一个优先级为 6,注意:系统所管理的优先级范围:5~15
将设计两个任务:start_task、task1
两个任务的功能如下
start_task:用来创建 task1 任务
task1:中断测试任务,任务中将调用关中断和开中断函数来体现对中断的管理作用
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "freertos_demo.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
key_init(); /* 初始化按键 */
freertos_demo();
}
freertos_demo.c
#include "freertos_demo.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 */
/******************************************************************************************************/
/* FreeRTOS例程入口函数 */
void freertos_demo(void)
{
lcd_show_string(10, 10, 220, 32, 32, "STM32", RED);
lcd_show_string(10, 47, 220, 24, 24, "Interrupt", RED);
lcd_show_string(10, 76, 220, 16, 16, "ATOM@ALIENTEK", RED);
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();
}
/* start_task */
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 初始化TIM3、TIM5 */
btim_tim3_int_init(10000 - 1, 8400 - 1);
btim_tim5_int_init(10000 - 1, 8400 - 1);
/* 创建任务1 */
xTaskCreate((TaskFunction_t)task1, /* 任务函数 */
(const char *)"task1", /* 任务名称 */
(uint16_t)TASK1_STK_SIZE, /* 任务堆栈大小 */
(void *)NULL, /* 传入给任务函数的参数 */
(UBaseType_t)TASK1_PRIO, /* 任务优先级 */
(TaskHandle_t *)&Task1Task_Handler); /* 任务句柄 */
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/* task1 */
void task1(void *pvParameters)
{
uint32_t task1_num = 0;
while (1)
{
if (++task1_num == 5)
{
printf("FreeRTOS Close Interrupt\r\n");
portDISABLE_INTERRUPTS(); /* FreeRTOS关闭中断 */
delay_ms(5000); /* 不能使用vTaskDelay,vTaskDelay内部会执行开中断操作 */
printf("FreeRTOS Open Interrupt\r\n");
portENABLE_INTERRUPTS(); /* FreeRTOS打开中断 */
task1_num = 0;
}
vTaskDelay(1000);
}
}
freertos_demo.h
#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_H
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/TIMER/btim.h"
#include "FreeRTOS.h"
#include "task.h"
void freertos_demo(void);
#endif
btim.c
#include "./BSP/TIMER/btim.h"
TIM_HandleTypeDef g_tim3_handle; /* 定时器3句柄 */
TIM_HandleTypeDef g_tim5_handle; /* 定时器5句柄 */
/* 基本定时器TIM3中断服务函数 */
void BTIM_TIM3_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_tim3_handle); /*定时器回调函数*/
}
/* 基本定时器TIM5中断服务函数 */
void BTIM_TIM5_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_tim5_handle); /*定时器回调函数*/
}
/* 回调函数,定时器中断服务函数调用 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == (&g_tim3_handle))
{
printf("TIM3 Output\r\n");
}
else if (htim == (&g_tim5_handle))
{
printf("TIM5 Output\r\n");
}
}
/* 基本定时器TIM3定时中断初始化函数 */
void btim_tim3_int_init(uint16_t arr, uint16_t psc)
{
g_tim3_handle.Instance = BTIM_TIM3_INT; /*通用定时器3*/
g_tim3_handle.Init.Prescaler = psc; /* 设置预分频器 */
g_tim3_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数器 */
g_tim3_handle.Init.Period = arr; /*自动装载值*/
g_tim3_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /*时钟分频因子*/
HAL_TIM_Base_Init(&g_tim3_handle);
HAL_TIM_Base_Start_IT(&g_tim3_handle); /*使能通用定时器3和及其更新中断:TIM_IT_UPDATE*/
}
/* 基本定时器TIM5定时中断初始化函数 */
void btim_tim5_int_init(uint16_t arr, uint16_t psc)
{
g_tim5_handle.Instance = BTIM_TIM5_INT; /*通用定时器5*/
g_tim5_handle.Init.Prescaler = psc; /* 设置预分频器 */
g_tim5_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数器 */
g_tim5_handle.Init.Period = arr; /*自动装载值*/
g_tim5_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /*时钟分频因子*/
HAL_TIM_Base_Init(&g_tim5_handle);
HAL_TIM_Base_Start_IT(&g_tim5_handle); /*使能通用定时器5和及其更新中断:TIM_IT_UPDATE*/
}
/* 定时器底层驱动,开启时钟,设置中断优先级 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == BTIM_TIM3_INT)
{
BTIM_TIM3_INT_CLK_ENABLE(); /*使能TIM时钟*/
HAL_NVIC_SetPriority(BTIM_TIM3_INT_IRQn, 4, 0); /* 抢占4,子优先级0,组4 */
HAL_NVIC_EnableIRQ(BTIM_TIM3_INT_IRQn); /*开启ITM3中断*/
}
else if (htim->Instance == BTIM_TIM5_INT)
{
BTIM_TIM5_INT_CLK_ENABLE(); /*使能TIM时钟*/
HAL_NVIC_SetPriority(BTIM_TIM5_INT_IRQn, 6, 0); /* 抢占6,子优先级0,组4 */
HAL_NVIC_EnableIRQ(BTIM_TIM5_INT_IRQn); /*开启ITM5中断*/
}
}
bitm.h
#ifndef _BTIM_H
#define _BTIM_H
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/USART/usart.h"
#include "./BSP/LED/led.h"
/******************************************************************************************/
/* 基本定时器 定义 */
/* TIMX 中断定义
* 默认是针对TIM6/TIM7
* 注意: 通过修改这4个宏定义,可以支持TIM1~TIM8任意一个定时器.
*/
#define BTIM_TIM3_INT TIM3
#define BTIM_TIM3_INT_IRQn TIM3_IRQn
#define BTIM_TIM3_INT_IRQHandler TIM3_IRQHandler
#define BTIM_TIM3_INT_CLK_ENABLE() do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0) /* TIM3 时钟使能 */
#define BTIM_TIM5_INT TIM5
#define BTIM_TIM5_INT_IRQn TIM5_IRQn
#define BTIM_TIM5_INT_IRQHandler TIM5_IRQHandler
#define BTIM_TIM5_INT_CLK_ENABLE() do{ __HAL_RCC_TIM5_CLK_ENABLE(); }while(0) /* TIM5 时钟使能 */
/******************************************************************************************/
void btim_tim3_int_init(uint16_t arr, uint16_t psc);
void btim_tim5_int_init(uint16_t arr, uint16_t psc);
#endif