欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

STM32中断及其优先级

程序员文章站 2024-02-25 14:51:39
...
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 的则全是外部中断。如下图示:
STM32中断及其优先级
STM32中断及其优先级

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

二、异常优先级

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

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

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

为了使抢占机能变得更可控,CM3 还把256 级优先级按位分成高低两段,分别是抢占优先级和子优先级
NVIC 中有一个寄存器是“应用程序中断及复位控制寄存器(AIRCR)”,它里面有一个位段名为“优先级组”:
STM32中断及其优先级
该位段的值对每一个优先级可配置的异常都有影响,把其优先级分为位段:MSB 所在的位段(左边的)对应抢占优先级,而LSB 所在的位段(右边的)对应子优先级,位数与分组位置的关系:如下图示:
STM32中断及其优先级
比如,如果优先级分组为1,则抢占优先级有64级[7:2],子优先级有4级[1:0]。

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

四、中断寄存器组

-->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;

ISERInterrupt 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 没有效果),从而实现每个中断都可以自顾地设置,而互不侵犯——只需单一的写指令,不再需要读-改-写。
ISPRInterrupt Set-Pending Registers,是一个中断挂起控制寄存器组。每个位对应的中断和 ISER 是一样的。通过置 1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写 0 是无效的。
ICPRInterrupt Clear-Pending Registers,是一个中断解挂控制寄存器组。其作用与 ISPR 相反,对应位也和 ISER 是一样的。通过设置 1,可以将挂起的中断解挂。写 0 无效。
IABRActive Bit Registers,是一个中断**标志位寄存器组。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。对应位所代表的中断和 ISER 一样,如果为 1,则表示该位所对应的中断正在被执行。
IPR Interrupt Priority Registers,是一个中断优先级控制的寄存器组。这个寄存器组相当重要! STM32 的中断分组与这个寄存器组密切相关。因为 STM32 的中断达 60 多个,所以 STM32 采用中断分组的办法来确定中断的优先级。 IPR 寄存器组由 240  8bit 的寄存器组成,每个可屏蔽中断占用 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 来定义的。具体的分配关系如下图 表所示: 
STM32中断及其优先级
通过这个表,我们就可以清楚的看到组 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);
	}
}