最近又遇到了使用Systick来产生精确计时,然后又涉及到了Systick的中断优先级的问题。我们老板认为Systick属于内核外
设,中断优先级应该比普通外设高。然后我们说,在网上看到的是,Systick的中断优先级ST官方默认设置15(1111b),然
后换算为4位(M4用4位来表示优先级分组)是3(抢占优先级),3(响应优先级),其实是最低的,但老板说虽然它是最低的,但它是跟CPU紧耦合的内核外设,它依然能打断优先级比它高的普通外设,当然我们也有点不确定网上说的就是对的(还是老板说的就是错的,==)。于是就做了一个对比实验,测试到底是Systick到底能否打断中断优先级比它高的。
实验环境条件如下:MDK v5.21a,stm32f429IGT6的MCU。
设置Systick的中断优先级为7(1,3),10ms产生一个中断,初始化Systick代码如下:
/**
* @brief 启动系统滴答定时器 SysTick
* @param 无
* @retval 无
*/
void SysTick_Init(void)
{
/* SystemFrequency / 1000 1ms中断一次
* SystemFrequency / 100000 10us中断一次
* SystemFrequency / 1000000 1us中断一次
*/
if (SysTick_Config(SystemCoreClock / 100))
{
/* Capture error */
while (1);
}
}
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
SysTick->LOAD = ticks - 1; /* set reload register */
// NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Systick Interrupt */
NVIC_SetPriority (SysTick_IRQn, 7); //设置Systick中断优先级为7(1,3)
SysTick->VAL = 0; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0); /* Function successful */
}
Systick的中断服务函数如下:
/*Systick中断服务函数*/
void SysTick_Handler(void)
{
LED_RED;
Delay(0xffff);//普通的软件延时
}
然后配置两个按键KEY1和KEY2,产生两个外部中断EXTI(上升沿中断),NVIC的Group分组为
Group2。分别设置KEY1的外部中断优先级为15(3,3),KEY2的外部中断优先级为3(0,3),代码如下:
void EXTI_Key_Config(void)
{
/*定义一个XXXX_InitTypeDef类型的结构体*/
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
/*开启按键对应GPIO*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOC,ENABLE);
/*开启SYSCFG时钟,外部中断需要*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
/*配置NVIC为优先级组1*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/*配置中断源:按键1*/
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
/*配置抢占优先级1*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
/*配置子优先级1*/
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
/*使能中断通道*/
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/*配置中断源:按键2*/
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
/*配置抢占优先级1*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
/*配置子优先级1*/
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_Init(&NVIC_InitStructure);
/*中断引脚配置*/
/*外部中断引脚配置为输入模式无上下拉*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/*按键2*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOC,&GPIO_InitStructure);
/*配置外部中断*/
/*将引脚连接到外部中断线上*/
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);
/* 选择EXTI中断源*/
EXTI_InitStructure. EXTI_Line = EXTI_Line0;
/*中断模式*/
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/*上升沿触发*/
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
/*使能中断*/
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/*按键2*/
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC,EXTI_PinSource13);
EXTI_InitStructure. EXTI_Line = EXTI_Line13;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/*下降沿触发*/
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
EXTI_ClearITPendingBit(EXTI_Line0);
EXTI_ClearITPendingBit(EXTI_Line13);
}
KEY1和KEY2的外部中断服务函数如下:
/*按键1中断服务函数*/
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) != RESET)
{
LED_GREEN;
while(1)
{
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
/*按键2中断服务函数*/
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line13) != RESET)
{
LED_WHITE
while(1)
{
}
EXTI_ClearITPendingBit(EXTI_Line13);
}
EXTI_ClearITPendingBit(EXTI_Line13);
}
最后是主函数的代码:
int main(void)
{
/* 初始化系统滴答定时器 */
SysTick_Init();
EXTI_Key_Config();
/* LED 端口初始化 */
LED_GPIO_Config();
/* 控制LED灯 */
while (1)
{
}
}
实验设计的条件如下:
中断 | KEY1 | Systick | KEY2 |
---|---|---|---|
优先级(抢占、响应) | 15(3,3) | 7(1,3) | 3(0,3) |
现象 | 亮绿灯 | 亮红灯 | 亮白灯 |
另外,从代码可以看出,KEY1和KEY2的中断服务函数都是亮灯后死循环(只是为了做实验而已,中断服务函数里绝不允许放死循环,应该服从“快进快出”的原则),Systick的中断服务函数是亮灯后延时一段时间。(亮灯都是控制的亮同一个灯,使之产生不同颜色,方便区分)
实验现象如下:
程序下到板子里开始跑后,未操作前,一直亮红灯,当第一次按下按键1后,会闪一下绿灯然后亮红灯,再按按键1,无反应(还是亮红灯);然后按下按键2,亮白灯,一直亮白灯,再按按键1,无反应(还是亮白灯)。
实验现象分析:
未操作前,亮红灯,因为每10ms产生一次Systick中断,红灯一次置位就会一直亮;
当按下KEY1后,会闪一下绿灯然后亮红灯,这是因为KEY1的中断优先级虽然比Systick低,但是当执行完Systick中断后还是会去执行KEY1的中断,称为“咬尾中断”,然后当Systick产生中断后再返回Systick中断;再按KEY1,无反应,是因为代码中在退出按键1中断前加了个死循环,未清除KEY1的中断标志位,所以进不去;
然后按下KEY2,一直亮白灯,是因为KEY2的中断优先级比Systick高,能抢占(虽然从灯上没体现出来),而且KEY2的中断里也加了个死循环,退不出来。也就是说,当KEY2的中断在执行的时候,虽然Systick的时间到了,产生了Systick中断,但还是打断不了KEY2的中断,因为优先级没它(KEY2)高。
实验结论:
Systick虽然作为与CPU紧耦合的内核外设,但其中断优先级并不比普通外设要高,并不因为它是内核外设而特殊,它还是遵循中断优先级高低的规则来响应。
实验注意:
设置Systick中断优先级的时候,不能通过修改__NVIC_PRIO_BITS的大小,比如改成3,使Systick中断优先级为7。不能那么改,而应该和上面代码一样,采用直接赋值的办法。因为==__NVIC_PRIO_BITS表示的是M4所有中断分组所用的位数,不是普通的设置成Systick中断优先级的数字==;
另外,NVIC分组设置,最好设置为Group2,两位表示抢占优先级(主优先级),两位表示响应优先级(子优先级),方便对比。