一、什么是中断
通俗点讲就是让CPU停止当前在做的事,转而去做更紧急的事。
二、中断优先级分组
这个紧急的事也有一个等级之分,优先级越高越先执行。stm32使用中断优先配置寄存器的高4位,共16级的中断优先等级。
stm32的中断优先等级可以分为抢占优先级和子优先级。抢占优先级高的中断可以打断正在执行的抢占优先级低的中断;当抢占优先级相同时,子优先级高的优先执行。
eg:抢占优先级2,子优先级1的中断可以打断抢占优先级3,子优先级0的中断;但抢占优先级2,子优先级1的中断不能打断抢占优先级2,子优先级2的中断;虽然前者子优先级高于后者,但子优先级不能打断其他中断。
三、特征
1、低于configMAX_SYSCALL_INTERRUPT_PRIORITY(一般默认设置为5)优先级的中断才允许调用FreeRTOS的API函数(带有“FromISR后缀的函数”);
2、将所有优先级位指定为抢占优先级位,方便管理;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
3、中断优先级越小越优先,任务优先级越大越优先;
四、FreeRTOS中断相关寄存器
FreeRTOS 中,通过 SHPR3 寄存器,将 PendSV 和 Systick 中断优先级被设置为最低,因为要保证系统任务的切换不会阻塞系统其他中断的响应。
1、FreeRTOS关闭中断:
FreeRTOS 中,通过 BASEPRI 寄存器,屏蔽优先级低于某一阈值的中断。
eg:BASEPRI 寄存器设置为0x50,表示中断优先级在5~15内的均被屏蔽,0~4的正常执行。(因为中断寄存器使用的是8位寄存器的高4位)也就是关闭FreeRTOS的中断 。
portDISABLE_INTERRUPTS(); //关闭中断
经过此函数的一层层回调,会发现最终此函数还是对寄存器 basepri 写入0x50来关闭中断;
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 //中断最低优先级
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 //系统可管理的最高中断优先级
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
//此处对5进行左移4位,正是因为控制中断的寄存器高4位有效static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */msr basepri, ulNewBASEPRI //将0x50写入寄存器basepridsbisb}
}
2、FreeRTOS开启中断:
将 BASEPRI 寄存器值设置为0时,则不关闭任何中断。
portENABLE_INTERRUPTS(); //开启中断
此处就比较简单了,与关闭中断函数类似,直接对中断寄存器 basepri 写0。
#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 tolower the BASEPRI value. */msr basepri, ulBASEPRI}
}
五、中断管理例程
创建函数与上一章说的类似,此处只展示 task1 代码:
注意:此处延时函数使用的是 delay_xms(); 而不是delay_ms(); 或 vTaskDelay(); 因为FreeRTOS提供的 vTaskDelay() 内部会调用到 vPortSetBASEPRI( 0 ) ,就是上面开启中断所调用的内部函数。也就是说 vTaskDelay() 会调用到开中断函数,而 delay_ms() 内部则是调用的 vTaskDelay() 函数。所以如果调用 vTaskDelay() 函数会提前开启中断,从而得不到正确的实验现象。
void task1( void * pvParameters )
{uint8_t task1_num=0;while(1){if(++task1_num==5){printf("关中断!!!\r\n");portDISABLE_INTERRUPTS(); //关闭中断delay_xms(5000);printf("开中断!!!!!!\r\n");portENABLE_INTERRUPTS(); //开启中断}vTaskDelay(1000);}
}
定时器部分,为了验证FreeRTOS的中断管理函数仅对5~15的优先级有效,所以 TIM3 的优先级为4,TIM5 的优先级为6;
void TIM3_Int_Init(u16 arr,u16 psc)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能//定时器TIM3初始化TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断//中断优先级NVIC设置NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4; //先占优先级4级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器TIM_Cmd(TIM3, ENABLE); //使能TIMx
}//通用定时器5中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器5!
void TIM5_Int_Init(u16 arr,u16 psc)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能//定时器TIM3初始化TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中断,允许更新中断//中断优先级NVIC设置NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM5中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6; //先占优先级6级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器TIM_Cmd(TIM5, ENABLE); //使能TIM5
}
中断服务函数
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断{printf("TIM3输出!!!\r\n");}TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}//定时器5中断服务函数
void TIM5_IRQHandler(void)
{if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET) //溢出中断{printf("TIM5输出!!!!!!\r\n");}TIM_ClearITPendingBit(TIM5,TIM_IT_Update); //清除中断标志位
}
由于FreeRTOS的中断管理函数仅对5~15的优先级有效,所以关闭中断后,TIM3会正常工作。
实验现象:
至于关中断时候输出的乱码,我个人觉得可能是抢占同一个串口导致的。