一、STM32 (Cortex-M3) 中的优先级概念
STM32(Cortex-M3)中有两个优先级的概念:抢占式优先级和响应优先级,也把响应优先级称作“亚优先级”或“副优先级”,每个中断源都需要被指定这两种优先级。
1. 何为占先式优先级(pre-emption priority)
高占先式优先级的中断事件会打断当前的主程序/中断程序运行—抢断式优先响应,俗称中断嵌套。
2. 何为副优先级(subpriority)
在占先式优先级相同的情况下,高副优先级的中断优先被响应;
在占先式优先级相同的情况下,如果有低副优先级中断正在执行, 高副优先级的中断要等待已被响应的低副优先级中断执行结束后才能得到响应—非抢断式响应(不能嵌套)。
3. 判断中断是否会被响应的依据
首先是占先式优先级,其次是副优先级;
占先式优先级决定是否会有中断嵌套;
Reset、NMI、Hard Fault 优先级为负(高于普通中断优先级)且不可调整。
4. 优先级冲突的处理
具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断的嵌套,或者说高抢占式优先级的中断可以嵌套低抢占式优先级的中断。
当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。
5. Cortex-M3中对中断优先级的定义
既然每个中断源都需要被指定这两种优先级,就需要有相应的寄存器位记录每个中断的优先级;在Cortex-M3中定义了8个比特位用于设置中断源的优先级,这8个比特位可以有8种分配方式,如下:
所有8位用于指定响应优先级
最高1位用于指定抢占式优先级,最低7位用于指定响应优先级
最高2位用于指定抢占式优先级,最低6位用于指定响应优先级
最高3位用于指定抢占式优先级,最低5位用于指定响应优先级
最高4位用于指定抢占式优先级,最低4位用于指定响应优先级
最高5位用于指定抢占式优先级,最低3位用于指定响应优先级
最高6位用于指定抢占式优先级,最低2位用于指定响应优先级
最高7位用于指定抢占式优先级,最低1位用于指定响应优先级
这就是优先级分组的概念。
6. stm32中对中断优先级的定义
Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,因此STM32把指定中断优先级的寄存器位减少到4位,这4个寄存器位的分组方式如下:
第0组:所有4位用于指定响应优先级
第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级
第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级
第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级
第4组:所有4位用于指定抢占式优先级
AIRC(Application Interrupt and Reset Register)寄存器中有用于指定优先级的 4 bits。这4个bits用于分配preemption优先级和sub优先级,在STM32的固件库中定义如下:
/* Preemption Priority Group */
#define NVIC_PriorityGroup_0 ((u32)0x700) /* 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((u32)0x600) /* 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((u32)0x500) /* 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((u32)0x400) /* 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((u32)0x300) /* 4 bits for pre-emption priority
0 bits for subpriority */
可以通过调用STM32的固件库中的函数NVIC_PriorityGroupConfig()选择使用哪种优先级分组方式,这个函数的参数有下列5种:
NVIC_PriorityGroup_0 => 选择第0组
NVIC_PriorityGroup_1 => 选择第1组
NVIC_PriorityGroup_2 => 选择第2组
NVIC_PriorityGroup_3 => 选择第3组
NVIC_PriorityGroup_4 => 选择第4组
接下来就是指定中断源的优先级,下面以一个简单的例子说明如何指定中断源的抢占式优先级和响应优先级:
// 选择使用优先级分组第1组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// 使能EXTI0中断
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 指定抢占式优先级别1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 指定响应优先级别0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能EXTI9_5中断
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 指定抢占式优先级别0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 指定响应优先级别1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
--------------------------------------------------------------------------------
要注意的几点是:
1. 如果指定的抢占式优先级别或响应优先级别超出了选定的优先级分组所限定的范围,将可能得到意想不到的结果;
2. 抢占式优先级别相同的中断源之间没有嵌套关系;
3. 如果某个中断源被指定为某个抢占式优先级别,又没有其它中断源处于同一个抢占式优先级别,则可以为这个中断源指定任意有效的响应优先级别。
二、开关总中断
在STM32/Cortex-M3中是通过改变CPU的当前优先级来允许或禁止中断。
PRIMASK位:只允许NMI和hard fault异常,其他中断/异常都被屏蔽(当前CPU优先级=0)。
FAULTMASK位:只允许NMI,其他所有中断/异常都被屏蔽(当前CPU优先级=-1)。
在STM32固件库中(stm32f10x_nvic.c和stm32f10x_nvic.h) 定义了四个函数操作PRIMASK位和FAULTMASK位,改变CPU的当前优先级,从而达到控制所有中断的目的。
下面两个函数等效于关闭总中断:
void NVIC_SETPRIMASK(void);
void NVIC_SETFAULTMASK(void);
下面两个函数等效于开放总中断:
void NVIC_RESETPRIMASK(void);
void NVIC_RESETFAULTMASK(void);
上面两组函数要成对使用,但不能交叉使用。
例如:
第一种方法:
NVIC_SETPRIMASK(); //关闭总中断
NVIC_RESETPRIMASK();//开放总中断
第二种方法:
NVIC_SETFAULTMASK(); //关闭总中断
NVIC_RESETFAULTMASK();//开放总中断
常常使用:
NVIC_SETPRIMASK(); // Disable Interrupts
NVIC_RESETPRIMASK(); // Enable Interrupts
-------------------------------------------------------------------------------------------------
补充:
可以用:
#define CLI() __set_PRIMASK(1)
#define SEI() __set_PRIMASK(0)
来实现开关总中断的功能。
NVIC例程程序
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_lib.h"
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
bool PreemptionOccured = FALSE;
u8 PreemptionPriorityValue = 0;
ErrorStatus HSEStartUpStatus;
/* Private function prototypes -----------------------------------------------*/
void RCC_Configuration(void);
void GPIO_Configuration(void);
void EXTI_Configuration(void);
void Delay(vu32 nCount);
/* Private functions ---------------------------------------------------------*/
int main(void)
{
#ifdef DEBUG
debug();
#endif
/* Configure the system clocks */
RCC_Configuration();
/* Configure GPIOs */
GPIO_Configuration();
/* Configures the EXTI Lines */
EXTI_Configuration();
#ifdef VECT_TAB_RAM
/* Set the Vector Table base location at 0x20000000 */
NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
#else /* VECT_TAB_FLASH */
/* Set the Vector Table base location at 0x08000000 */
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);
#endif
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //先占优先级分组为1
/* Enable the EXTI0 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQChannel;// 外部中断线0中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = PreemptionPriorityValue;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Enable the EXTI9_5 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Configure the SysTick Handler Priority: Preemption priority and subpriority */
NVIC_SystemHandlerPriorityConfig(SystemHandler_SysTick, !PreemptionPriorityValue, 0);
while (1)
{
if(PreemptionOccured != FALSE)
{
GPIO_WriteBit(GPIOC, GPIO_Pin_6, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_6)));
Delay(0x5FFFF);
GPIO_WriteBit(GPIOC, GPIO_Pin_7, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_7)));
Delay(0x5FFFF);
GPIO_WriteBit(GPIOC, GPIO_Pin_8, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_8)));
Delay(0x5FFFF);
GPIO_WriteBit(GPIOC, GPIO_Pin_9, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_9)));
Delay(0x5FFFF);
}
} //GPIO_WriteBit(GPIO_TypeDef* GPIOx, u16 GPIO_Pin, BitAction BitVal)设置或者清除指定的数据端口位
}
void RCC_Configuration(void)
{
/* RCC system reset(for debug purpose) */
RCC_DeInit();
/* Enable HSE */
RCC_HSEConfig(RCC_HSE_ON);
/* Wait till HSE is ready */
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(HSEStartUpStatus == SUCCESS)
{
/* HCLK = SYSCLK */
RCC_HCLKConfig(RCC_SYSCLK_Div1);
/* PCLK2 = HCLK */
RCC_PCLK2Config(RCC_HCLK_Div1);
/* PCLK1 = HCLK/2 */
RCC_PCLK1Config(RCC_HCLK_Div2);
/* Flash 2 wait state */
FLASH_SetLatency(FLASH_Latency_2); //FLASH_Latency用来设置FLASH存储器延时时钟周期数2
/* Enable Prefetch Buffer */
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); //使能或者失能预取指缓存
/* PLLCLK = 8MHz * 9 = 72 MHz */ //PLL的输入时钟 = HSE时钟频率,
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
/* Enable PLL */
RCC_PLLCmd(ENABLE);
/* Wait till PLL is ready */
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
} //当设置完成时,RCC_FLAG_PLLRDY返回SET(1)
/* Select PLL as system clock source */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* Wait till PLL is used as system clock source */
while(RCC_GetSYSCLKSource() != 0x08)
{
}
}
/* Enable GPIOA, GPIOB, GPIOC and AFIO Clocks */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
}
void GPIO_Configuration(void)
{
/* Configure PC6, PC7, PC8 and PC9 as output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* Configure GPIOA Pin0 as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure GPIOB Pin9 as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void EXTI_Configuration(void)
{
/* Connect EXTI Line0 to PA.00 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
/* Configure EXTI Line0 to generate an interrupt on falling edge */
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/* Connect EXTI Line9 to PB.09 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource9);
/* Configure EXTI Line9 to generate an interrupt on falling edge */
EXTI_InitStructure.EXTI_Line = EXTI_Line9;
EXTI_Init(&EXTI_InitStructure);
}
void Delay(vu32 nCount)
{
for(; nCount != 0; nCount--);
}
#ifdef DEBUG
void assert_failed(u8* file, u32 line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif
1设计要求
设计一个中断优先级抢占的实例。设置三个中断:EXTI0、EXTI9和SysTick,在EXTI9的中断服务之程序
中实现EXTI1和SysTick的优先级别的转换,使之分别出现:在EXTI1中断时可以被SysTick抢占和不可以
被SysTick抢占这两种状态。
2 硬件电路设计
在评估板上按键Key与PB.9相连作为EXTI9,按键Wakeup与PA.0相连作为EXTI1;LED1、LED2、LED3、LED4
分别与PC6、PC7、PC8、PC9相连,用于显示不同的优先级抢占状态。
3 软件程序设计
根据设计任务要求,软件程序主要内容包括:
(1) 配置两根EXTI 外部中断线(Line0 和 Line9),在下降沿产生中断,并配置SysTick中断。其中:
EXTI0: 优先级= PreemptionPriorityValue,子优先级=0;
EXTI9: 优先级= 0,子优先级= 1;
SysTick Handler:优先级= !PreemptionPriorityValue,子优先级 SubPriority = 0;
(2) 刚开始设置PreemptionPriorityValue=0,即EXTI0优先级比SysTick优先级高;在EXTI9中断服务子
程序中,EXTI Line0和SysTick 的优先级对换;
(3) 在EXTI0中断服务子程序中,SysTick中断挂起位被置1,若SysTick优先级比EXTI Line0 优先级高,
则EXTI 0中断被抢占,转而去执行SysTick中断服务子程序;否则继续执行EXTI Line0中断服务子程序。
(4) 如果EXTI1被SysTick抢占则LED1、LED2、LED3、LED4循环闪烁,如果EXTI1抢占SysTick则LED1、
LED2、LED3、LED4常亮。
4 运行过程
(1) 使用Keil uVision3 通过ULINK仿真器连接实验板,打开实验例程NVIC_test子目录下的NVIC.Uv2
例程,编译链接工程;
(2) 点击MDK 的Debug菜单,选择Start/Stop Debug Session项或Ctrl+F5键,远程连接目标板并下载
调试代码到目标系统的RAM中;
(3) 程序正常启动运行后,会有以下结果:
当第一次发生EXTI9 中断后(按下STM32板上Key按钮),SysTick中断的优先级比EXTI0中断
优先级高。因此当EXTI0中断发生时(按下Wakeup按钮),将先执行SysTick中断服务子程序,
发生抢占,变量PreemptionOccured 为真,LED1-LED4开始闪烁;
当第二次发生EXTI9中断后,SysTick中断的占优先级比EXTI0优先级低,因此当EXTI0中断发
生时SysTick无法抢占,变量PreemptionOccured 为假,LED1-LED4停止闪烁;
每次EXTI9发生后,SysTick和EXTI0就会发生优先级转换,出现前面2步的状态。