STM32中断及其优先级



STM32中断配置示例:
/* NVIC config */
void NVIC_config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);	

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);	 
}

这里涉及异常(中断)相关的:
1、异常与中断
2、优先级分组,NVIC_PriorityGroupConfig
3、抢占优先级,NVIC_IRQChannelPreemptionPriority
4、子(亚)优先级,NVIC_IRQChannelSubPriority
5、中断号定义,NVIC_IRQChannel
6、优先级配置的寄存器实现,NVIC_Init()的实现
此篇对上述内容进行整理,以下内容参考资料《Cortex-M3权威指南》

一、Cortex-M3异常简介

CM3  在内核水平上搭载了一个异常响应系统,所有中断机制都由NVIC实现。CM3有256个预定义的异常(所有能打断正常执行流的事件都称为异常)类型 其中编号为 1 - 15  的对应系统异常,大于等于 16  的则全是外部中断。如下图示:



虽然CM3 是支持240 个外中断的,但具体使用了多少个是由芯片生产商决定。比如:STM32 84 个中断,包括16 个内核中断和68 个可屏蔽中断。不同系列支持的中断数也是不同的,需参考具体芯片datasheet。

二、异常优先级

除了个别异常(复位、NMI、硬fault)的优先级被定死外,其它异常的优先级都是可编程的。
CM3 中,优先级对于异常来说很关键的,它会影响一个异常是否能被响应,以及何时可以响应。优先级的数值越小,则优先级越高。CM3 支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。 
CM3 允许的最少使用位数为3 个位(位序MSB),亦即至少要支持8 级优先级。举例来说,如果只使用了3 个位来表达优先级,则优先级配置寄存器的结构会如图所示:


在图中, [4:0] 没有被实现,所以读它们总是返回零,写它们则忽略写入的值。因此,对3 个位的情况,我们能够使用的8 个优先级为:0x00(最高),0x200x400x600x800xA00xC0 以及0xE0
注:STM32使用3位,即[7:4]表示优先级,所以STM32具有16级优先级。

三、优先级分组:抢占优先级和子(亚)优先级

为了使抢占机能变得更可控,CM3 还把256 级优先级按位分成高低两段,分别是抢占优先级和子优先级
NVIC 中有一个寄存器是“应用程序中断及复位控制寄存器(AIRCR)”,它里面有一个位段名为“优先级组”:

该位段的值对每一个优先级可配置的异常都有影响,把其优先级分为位段: MSB  所在的位段(左边的)对应抢占优先级,而 LSB  所在的位段(右边的)对应子优先级,位数与分组位置的关系:如下图示:

比如,如果优先级分组为1,则抢占优先级有64级[7:2],子优先级有4级[1:0]。

在计算抢占优先级和子优先级的有效位数时,必须先求出下列值:
1、芯片实际使用了多少位来表达优先级
2、优先级组是如何划分的。
举个例子,如果只使用
3  个位来表达优先级( [7:5] ),并且优先级组的值是 5 (从比特 5 处分组),则你得到 4  级抢占优先级,且在每个抢占优先级的内部有 2  个子优先级,如图 所示:


四、中断寄存器组

-->NVIC = ((NVIC_Type *) 0xE000E100)
typedef struct {
  __IO uint32_t ISER[8]; /*!< Offset: 0x000  Interrupt Set Enable Register          */
       uint32_t RESERVED0[24];
  __IO uint32_t ICER[8]; /*!< Offset: 0x080  Interrupt Clear Enable Register        */
       uint32_t RSERVED1[24];
  __IO uint32_t ISPR[8]; /*!< Offset: 0x100  Interrupt Set Pending Register          */
       uint32_t RESERVED2[24];
  __IO uint32_t ICPR[8]; /*!< Offset: 0x180  Interrupt Clear Pending Register        */
       uint32_t RESERVED3[24];
  __IO uint32_t IABR[8]; /*!< Offset: 0x200  Interrupt Active bit Register          */
       uint32_t RESERVED4[56];
  __IO uint8_t  IP[240]; /*!< Offset: 0x300  Interrupt Priority Register (8Bit wide) */
       uint32_t RESERVED5[644];
  __O  uint32_t STIR; /*!< Offset: 0xE00  Software Trigger Interrupt Register    */
}  NVIC_Type;

ISER Interrupt Set-Enable Registers ,中断使能寄存器组。 这里用了  2    32  位的寄存器,总共可以表示  64  个中断。而  STM32F103  只用了其中的前  60  位。  ISER[0]   bit0~bit31  分别对应中断  0~31, ISER[1]   bit0~27  对应中断  32~59 ;依次类推,这样总共  60  个中断就分别对应上了。要使能某个中断,必须设置相应的  ISER  位为  1。
ICER   Interrupt Clear-Enable Registers ,是一个中断除能寄存器组。该寄存器组与  ISER  的作用恰好相反,是用来清除某个中断的使能的 。这里要专门设置一个  ICER  来清除中断位,而不是向  ISER    0  来清除,是因为  NVIC  的这些寄存器都是写  1  有效的,写  0  是无效的。通过这种方式,使能/除能中断时只需把“当事位”写成 1,其它的位可以全部为零。再也不用像以前那样,害怕有些位被写入 0 而破坏其对应的中 断设置(写 0 没有效果),从而实现每个中断都可以自顾地设置,而互不侵犯——只需单一 的写指令,不再需要读-改-写。
ISPR Interrupt Set-Pending Registers ,是一个中断挂起控制寄存器组。每个位对应的中断和  ISER  是一样的。通过置  1 ,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写  0  是无效的。
ICPR Interrupt Clear-Pending Registers ,是一个中断解挂控制寄存器组。其作用与  ISPR  相反,对应位也和  ISER  是一样的。通过设置  1 ,可以将挂起的中断解挂。写  0  无效。
IABR Active Bit Registers ,是一个中断激活标志位寄存器组。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。对应位所代表的中断和  ISER  一样,如果为  1 ,则表示该位所对应的中断正在被执行。
IPR   Interrupt Priority Registers ,是一个中断优先级控制的寄存器组。这个寄存器组相当重要!  STM32  的中断分组与这个寄存器组密切相关。因为  STM32  的中断达  60  多个,所以  STM32  采用中断分组的办法来确定中断的优先级。  IPR  寄存器组由 240    8 bit  的寄存器组成,每个可屏蔽中断占用  8bit ,这样总共可以表示 240 个可屏蔽中断。
STIR:Software Trigger Interrupt Register,向软件触发中断寄存器STIR 的INTID字段([8:0])写入中断ID即可触发相应的中断。
Besides using NVIC-ISPR[n] registers, you can also use a Software Trigger Interrupt Register (NVIC->STIR) to trigger an interrupt using software:
For example, you can generate interrupt #3 by writing the following code in C:
NVIC->STIR = 3;

五、STM32中断实现分析

STM32 的中断分组: STM32 将中断分为 5 个组,组 0~4。该分组的设置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的。具体的分配关系如下图 表所示: 

通过这个表,我们就可以清楚的看到组  0~4  对应的配置关系,例如组设置为  3 ,那么此时所有的  60  个中断,每个中断的中断优先寄存器的高四位中的最高  3  位是抢占优先级,低  1  位是
响应优先级。  
1、中断分组设置
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
	/* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
	SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

直接把分组数值写入AIRCR寄存器的[10:8]位即可,写该寄存器需要一个Key值,即AIRCR_VECTKEY_MASK。

2、指定中断线设置
有了上述大篇幅的知识加上注释,STM32中断寄存器配置也就一目了然:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
{
	uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;
	if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE) {
		/* Compute the Corresponding IRQ Priority --------------------------------*/
		tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700)) >> 0x08; /* 中断分组值 */
		tmppre = (0x4 - tmppriority); /* 抢占优先级转化为[7:4]左移的位数 *
		tmpsub = tmpsub >> tmppriority; /* 子优先级掩码 */

		tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre; /* 抢占优先级,在[7:4]中的值 */
		tmppriority |= NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub; /* 子优先级 */
		tmppriority = tmppriority << 0x04; /* 最终优先级,在[7:0]中的值 */

        /* 中断号和其占用优先级寄存器一一对应 */
		NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;

		/* [NVIC_IRQChannel >> 0x05]定位ISER数组的下标,一个寄存器对应32个中断
         * [NVIC_IRQChannel & (uint8_t)0x1F)]定位ISER[x]的指定位
         */
		NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
			(uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
	} else {
		/* Disable the Selected IRQ Channels -------------------------------------*/
		NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
			(uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
	}
}
相关文章
相关标签/搜索