打断CPU正常运行程序,转而处理紧急的事件(中断服务函数)。
中断执行机制3步
1、中断请求
2、响应中断
3、退出中断
cortex-M使用8位寄存器配置中断优先级
stm32只用到高4位
stm32优先级分为抢占优先级和子优先级
抢占:高优先级抢低优先级
子优先级:同时抢占优先级,子优先级数值越小越优先执行。
优先级分组设置
特点:
1、通过调用函数HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)即可完成设置
在HAL_Init中设置
2、低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断里才允许调用FreeRTOS 的API函数
3、中断优先级数值越小越优先,任务优先级数值越大越优先
#如何配置pendsv处理任务切换和systick中断优先级
中断优先级寄存器
表出自:《Cortex M3权威指南(中文)》第286页
三个系统中断优先级配置寄存器,分别为 SHPR1、 SHPR2、 SHPR3
SHPR1寄存器地址:0xE000ED18
SHPR2寄存器地址:0xE000ED1C
SHPR3寄存器地址:0xE000ED20
这里补充个知识
1、一个地址对应一个存储单元,一个存储单元一般对应8位所以四个单元是32位也就是说用8位表示优先级而八位中又只用到高4位。
2、比如SHPR3这个32位寄存器排列应该是0xE000ED23-0xE000ED20
这里来解释为什么要左移4位 和左移16位24 位
FreeRTOS如何配置PendSV和Systick中断优先级?
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
#define portNVIC_PENDSV_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
#define portNVIC_SYSTICK_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )
//portNVIC_SHPR3_REG 的地址
#define portNVIC_SHPR3_REG ( *( ( volatile uint32_t * ) 0xe000ed20 ) )
#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) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_API_CALL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY
以上这些是设置好的宏在port.c中已经写好了。
FreeRTOS所使用的中断管理就是利用的BASEPRI这个寄存器
BASEPRI:屏蔽优先级低于某一个阈值的中断
比如: BASEPRI设置为0x50,代表中断优先级在5-15内的均被屏蔽,0~4的中断优先级正常执行
BASEPRI:屏蔽优先级低于某一个阈值的中断,当设置为0时,则不关闭任何中断(在上面的宏中我们实现了系统抵达定时器中断和pendsv中断优先级为15,而且设置5-15内的优先级能被屏蔽)
开中断
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
msr basepri, ulNewBASEPRI
dsb
isb
}
}
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /* FreeRTOS可管理的最高中断优先级 */
关中断
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
msr basepri, ulBASEPRI
}
}
所以我们关中断和开中断只需要调用portDISABLE_INTERRUPTS()
portENABLE_INTERRUPTS()就行。
本实验会使用两个定时器,一个优先级为4,一个优先级为6,注意:系统所管理的优先级范围:5~15,
现象:两个定时器每1s,打印一段字符串,当关中断时,停止打印,开中断时持续打印。
start_task 用来创建task1任务
task1 中断测试任务,任务中将调用关中断和开中断函数来体现对中断的管理作用!
这里只放部分代码
任务1
void task1( void * pvParameters )
{
uint8_t task1_num = 0;
while(1)
{
//前置自增先自增后使用
if(++task1_num==5)
{
task1_num=0;
portDISABLE_INTERRUPTS();
delay_ms(5000);//这里必须是delay_ms不能是vTaskDelay 因为vTaskDelay会开中断
portENABLE_INTERRUPTS();
}
vTaskDelay(1000);
}
}
初始化
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/btim.h"
#include "stdio.h"
TIM_HandleTypeDef g_timx_handler; /* 定时器参数句柄 */
TIM_HandleTypeDef g_tim7_handler; /* 定时器参数句柄 */
/**
* @brief 基本定时器TIMX定时中断初始化函数
* @note
* 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 基本定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr : 自动重装值。
* @param psc : 时钟预分频数
* @retval 无
*/
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
g_timx_handler.Instance = BTIM_TIMX_INT; /* 定时器x */
g_timx_handler.Init.Prescaler = psc; /* 分频 */
g_timx_handler.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_handler.Init.Period = arr; /* 自动装载值 */
HAL_TIM_Base_Init(&g_timx_handler);
HAL_TIM_Base_Start_IT(&g_timx_handler); /* 使能定时器x和定时器更新中断 */
}
/**
* @brief 基本定时器TIM7定时中断初始化函数
* @note
* 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 基本定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr : 自动重装值。
* @param psc : 时钟预分频数
* @retval 无
*/
void btim_tim7_int_init(uint16_t arr, uint16_t psc)
{
g_tim7_handler.Instance = BTIM_TIM7_INT; /* 定时器x */
g_tim7_handler.Init.Prescaler = psc; /* 分频 */
g_tim7_handler.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_tim7_handler.Init.Period = arr; /* 自动装载值 */
HAL_TIM_Base_Init(&g_tim7_handler);
HAL_TIM_Base_Start_IT(&g_tim7_handler); /* 使能定时器x和定时器更新中断 */
}
/**
* @brief 定时器底层驱动,开启时钟,设置中断优先级
此函数会被HAL_TIM_Base_Init()函数调用
* @param 无
* @retval 无
*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == BTIM_TIMX_INT)
{
BTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIMx时钟 */
HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 4, 0); /* 抢占1,子优先级3 */
HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn); /* 开启ITMx中断 */
}
if (htim->Instance == BTIM_TIM7_INT)
{
BTIM_TIM7_INT_CLK_ENABLE(); /* 使能TIMx时钟 */
HAL_NVIC_SetPriority(BTIM_TIM7_INT_IRQn, 6, 0); /* 抢占1,子优先级3 */
HAL_NVIC_EnableIRQ(BTIM_TIM7_INT_IRQn); /* 开启ITMx中断 */
}
}
/**
* @brief 基本定时器TIMX中断服务函数
* @param 无
* @retval 无
*/
void BTIM_TIMX_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_handler); /* 定时器回调函数 */
}
/**
* @brief 基本定时器TIMX中断服务函数
* @param 无
* @retval 无
*/
void BTIM_TIM7_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_tim7_handler); /* 定时器回调函数 */
}
/**
* @brief 回调函数,定时器中断服务函数调用
* @param 无
* @retval 无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
//无论哪个定时器处理计数溢出中断都会调用这个回调函数
if (htim->Instance == BTIM_TIMX_INT)
{
printf("TIM6优先级为4的正在运行!!!!\r\n");
}
if (htim->Instance == BTIM_TIM7_INT)
{
printf("TIM7优先级为6的正在运行!!!!\r\n");
}
}
注意printf耗时长请尽量不要在中断回调函数中调用。