stm32中断详解

stm32中断详解

1.什么是中断?

中断是在处理器执行程序过程中,遇到需要立即处理的内外部紧急事件时,暂时中断当前任务,转而执行专门的处理程序来应对这些事件的一种机制。一旦事件处理完成,处理器会自动返回到之前被中断的地方继续执行原来的程序。这种机制提高了CPU的运行效率,避免了因不断轮询等待事件状态改变而浪费资源,并且能够实现实时处理和多任务管理,在嵌入式系统中尤为重要。
在这里插入图片描述

1.STM32中断系统特点

STM32微控制器的中断系统非常强大且灵活,具备以下特点:

  • 中断源丰富:STM32拥有众多内置和外部中断源,包括定时器、串口、I²C、SPI、DMA、ADC以及每个GPIO引脚都可配置为中断源。

  • 中断控制器:采用Nested Vectored Interrupt Controller (NVIC),支持多级中断嵌套和优先级管理,允许在处理中断时响应更高级别的中断请求。

  • 中断优先级管理:STM32允许用户通过编程配置中断的优先级,支持优先级分组,以适应不同的应用需求。优先级分组将优先级分为抢占优先级和子优先级,以实现复杂的中断处理逻辑。

  • 中断向量表:存储中断服务例程(ISR)的入口地址,可以重映射到SRAM中以提高中断响应速度。

  • EXTI:External Interrupt/Event Controller,用于管理外部中断和事件,使得每个GPIO都能作为中断输入,增加了灵活性。

2.中断处理流程

  1. 中断请求:当某个事件发生时,相应的中断请求被触发。
  2. 中断响应:如果该中断未被屏蔽且优先级高于当前正在执行的任务,CPU将保存当前状态(如PC指针和寄存器内容),然后跳转到中断向量表执行中断服务例程(ISR)。
  3. 中断服务:ISR执行,处理中断事件。
  4. 中断返回:ISR执行完毕后,CPU恢复之前保存的状态,继续执行被打断的任务。

3.中断配置与使用

在编程STM32时,通常需要通过配置寄存器来使能中断、设置中断优先级,并编写对应的中断服务例程(ISR)。可以使用标准库、HAL库或LL库等不同层次的API来简化配置过程。

2.AFIO寄存器

AFIO寄存器在中断处理中的主要用途是实现GPIO引脚到外部中断线(EXTI)的映射。STM32的外部中断/事件控制器(EXTI)可以连接到多个GPIO引脚,但并不是每个GPIO直接连接到一个独立的中断线。因此,需要通过AFIO寄存器来指定哪些GPIO引脚的信号应该被路由到EXTI的哪一条中断线上。

AFIO中的外部中断配置寄存器(EXTICR1至EXTICR4)用于配置GPIO与EXTI线的关联。每个EXTICR寄存器有4组设置位,每组4位,分别对应4个GPIO端口(PA至PG)。通过设置这些位,可以决定当某个GPIO端口上的信号变化时,应触发哪个EXTI线的中断。

例如,如果你想将PA0设置为中断输入,并让它触发EXTI线0,就需要在AFIO_EXTICR1的EXTI0[3:0]位中设置相应的值,指示PA0作为EXTI0的输入源。这样,当PA0上发生预设的中断事件(如上升沿、下降沿或双边沿)时,EXTI0中断就会被触发。

AFIO寄存器在中断处理中的核心作用是确保GPIO信号能够正确地连接到EXTI系统,进而触发中断,使得软件能够及时响应外部事件。

3.NVIC寄存器

在STM32等基于ARM Cortex-M系列的微控制器中,NVIC(Nested Vectored Interrupt Controller)是中断管理系统的核心组件,它包含了一系列寄存器用于控制中断的使能、优先级、状态以及中断处理流程。以下是NVIC中几个关键寄存器的简要说明:

  1. 中断使能寄存器(ISER[0-1])

    • 这些寄存器用于使能中断。每个中断在ISER中有对应的一个位,写1到该位置可以使能相应的中断。STM32F103XX有两组ISER寄存器,每组控制32个中断,共管理64个中断。
  2. 中断除能寄存器(ICER[0-1])

    • 与ISER相反,这些寄存器用于除能中断。写1到对应位会禁止相应中断。
  3. 中断挂起寄存器(ISPR[0-1])

    • 当中断被挂起时(即中断产生但尚未处理,因为有更高优先级的中断正在处理),对应位会被置1。
  4. 中断解挂寄存器(ICPR[0-1])

    • 用于解除中断挂起状态,写1到对应位可以解挂一个已挂起的中断。
  5. 中断激活标志寄存器(IABR[0-1])

    • 反映当前正在执行的中断状态,如果某个中断正在被服务,则其对应位为1。
  6. 中断优先级寄存器(IPRx [0-4])

    • 这些寄存器用于设置每个中断的优先级。根据优先级分组配置,每个中断占用寄存器中的1到4位,用于设置抢占优先级和响应优先级。
  7. 中断控制与状态寄存器(ICSR)

    • 包含控制位和状态位,用于控制中断的处理流程,如软件触发中断、读取当前正在执行的中断等。
  8. 系统控制块的中断控制寄存器(SCB_AIRCR)

    • 控制整个系统的中断行为,包括中断优先级分组的设置(PRIGROUP位),以及系统复位等。
  9. 中断清除挂起寄存器(ICSR)中的相关位

    • 用于清除挂起的中断标志,使得中断可以被再次触发。

3.中断分组、抢占优先级和响应优先级

在STM32中断系统中,为了更好地管理中断处理的顺序和优先级,采用了中断分组、抢占优先级和响应优先级的概念。

1. 中断分组

中断分组是指将中断优先级分成抢占优先级和响应优先级两个部分,通过配置系统控制寄存器(SYSCFG)中的相关位来确定分组方式。STM32允许用户选择4种不同的分组模式:

  • 分组0:所有中断只有抢占优先级,没有响应优先级,最多支持16个优先级。
  • 分组1:每个中断由4位表示优先级,其中最高2位表示抢占优先级,低2位表示响应优先级,最多支持4个抢占优先级和4个响应优先级。
  • 分组2:每个中断由4位表示优先级,其中最高3位表示抢占优先级,最低1位表示响应优先级,最多支持8个抢占优先级和2个响应优先级。
  • 分组3分组4:每个中断由4位表示优先级,其中最高4位表示抢占优先级,没有响应优先级,最多支持16个抢占优先级。
    在这里插入图片描述

2. 抢占优先级

抢占优先级决定了中断能否打断其他中断服务程序的执行。一个高抢占优先级的中断可以打断低抢占优先级中断的服务,即使后者正在执行。当一个更高优先级的中断到来时,当前中断服务会被暂停,处理器先处理更高优先级的中断。

3. 响应优先级

响应优先级用于管理同级抢占优先级中断的执行顺序。当两个中断的抢占优先级相同,但一个响应优先级高于另一个时,响应优先级高的中断会优先得到服务。如果两个中断的抢占优先级和响应优先级都相同,那么按照中断产生的先后顺序处理。

4.配置与应用

在实际应用中,通过编程配置中断分组和各个中断的优先级,可以满足不同应用场景对实时性和任务调度的需求。通常使用库函数如HAL库的HAL_NVIC_SetPriority()函数来设置中断的优先级,同时考虑系统的实时性要求和中断处理的复杂度来合理安排中断分组和优先级配置。正确设置中断优先级和分组是保证嵌入式系统高效稳定运行的关键之一。

4.中断服务函数

中断服务函数(Interrupt Service Routine, ISR)是嵌入式系统编程中的关键部分,特别是在使用STM32等微控制器时。当一个中断事件发生时,处理器会暂停当前正在执行的任务,保存现场(即当前的工作状态),然后跳转到相应的中断服务函数去处理这个事件。处理完成后,处理器恢复之前保存的现场并继续执行被打断的任务。以下是关于STM32中断服务函数的一些关键特点和注意事项:

  1. 命名规则:STM32的中断服务函数遵循特定的命名约定,这些函数名通常在启动文件(如startup_stm32f10x_hd.sstartup_stm32f40_41xxx.s)中预定义。例如,USART1_IRQHandler是USART1中断的服务函数,EXTI0_IRQHandler对应EXTI线0的中断。

  2. 无返回值与形参:中断服务函数没有返回值,也不接受任何参数。这是因为中断可能在任何时刻发生,无法预测调用环境,所以不能依赖于常规的函数调用约定。

  3. 弱定义:在启动文件中,中断服务函数常常以.weak属性声明,这意味着如果用户没有提供具体的实现,编译器会提供一个默认的空实现(通常只是返回)。用户需要在自己的代码中重新定义这些函数以添加实际的中断处理逻辑。

  4. 执行速度:中断服务函数应尽可能简短且快速执行,以减少中断延迟并尽快让系统回到正常运行状态。复杂的处理应当委托给普通任务或使用中断后处理机制。

  5. 中断优先级:STM32支持多个级别的中断优先级管理,开发者可以通过配置NVIC(Nested Vectored Interrupt Controller)来设定每个中断的优先级,决定中断的响应顺序。

  6. 中断禁止与使能:在中断服务函数内部,有时需要临时禁止某些中断,以防止中断嵌套引起的问题。处理完毕后,应恢复中断使能状态。

  7. 保存与恢复上下文:对于需要执行多条指令的复杂ISR,可能需要显式保存和恢复CPU寄存器的值,以避免破坏中断前的程序状态。不过,Cortex-M内核提供了自动保存和恢复某些寄存器的功能(通过使用PendSV或其它机制)。

编写中断服务函数时,遵循最佳实践是非常重要的,以确保系统的稳定性和可靠性。

5.配置中断流程

1.配置外设

这里以stm32F103x的两个外部按键为例

	RCC_APB2PeriphClockCmd(KEY_GPIO_CLK, ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin = KEY1_GPIO_PIN|KEY2_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);

首先打开总线时钟,配置引脚以及引脚输出模式,这部分内容比较简单,这里不再详细赘述。

2.配置AFIO寄存器

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource4);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource5);
  1. RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

    这行代码的作用是使能高级控制外设时钟(APB2总线上的时钟)给AFIO(alternate function input/output,备用功能输入输出)模块。在STM32中,AFIO模块负责重映射和中断线的配置,比如将GPIO引脚映射到外部中断线上。调用RCC_APB2PeriphClockCmd函数并传入RCC_APB2Periph_AFIO作为参数,以及ENABLE标志,是为了确保在接下来对AFIO进行配置之前,相关的时钟已经被开启。

  2. GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource4);

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource5);

    这两行代码用于配置GPIO引脚作为外部中断线(EXTI)的输入。GPIO_EXTILineConfig函数有两个参数,第一个参数指定GPIO端口来源,第二个参数指定该端口上的具体引脚。在第一个调用中,GPIOC端口的第4号引脚(PC4)被配置为与某个EXTI线相连;在第二个调用中,配置的是GPIOC端口的第5号引脚(PC5)。

    通过这些配置,当PC4或PC5上的信号发生变化时,将会触发对应的EXTI中断。

3.配置EXTI

	EXTI_InitTypeDef EXTI_InitStruct;
	EXTI_InitStruct.EXTI_Line = EXTI_Line4 | EXTI_Line5;
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;
	
	EXTI_Init(&EXTI_InitStruct);

  1. EXTI_InitTypeDef EXTI_InitStruct;
    这行代码声明了一个EXTI_InitTypeDef类型的结构体变量EXTI_InitStruct,该结构体包含了EXTI(External Interrupt/Event)的所有初始化配置参数。

  2. EXTI_InitStruct.EXTI_Line = EXTI_Line4 | EXTI_Line5;
    这里设置了要配置的外部中断线。通过按位或运算符|,同时选择了EXTI线4(EXTI_Line4)和EXTI线5(EXTI_Line5),意味着接下来的配置将应用于这两个中断线。

  3. EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
    配置EXTI的工作模式为中断模式(EXTI_Mode_Interrupt)。EXTI可以工作在中断模式或事件模式,这里选择的是中断模式,意味着当指定的事件发生时,会触发CPU中断。

  4. EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
    设置触发条件为上升沿触发(EXTI_Trigger_Rising)。EXTI可以配置为上升沿、下降沿、双边沿或电平触发。这里配置的是当EXTI线上的信号从低到高变化(上升沿)时触发中断。

  5. EXTI_InitStruct.EXTI_LineCmd = ENABLE;
    启用所选的EXTI线(EXTI_Line4和EXTI_Line5)。通过设置EXTI_LineCmdENABLE,确保这些中断线在配置后处于启用状态。

  6. EXTI_Init(&EXTI_InitStruct);
    最后,调用EXTI_Init函数并将EXTI_InitStruct结构体的地址作为参数传递,完成EXTI的初始化配置。这个函数根据结构体中的设置,对EXTI模块进行相应的配置,使得指定的中断线按照所配置的模式、触发条件等开始工作。

4.配置NVIC

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority =1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

	NVIC_Init(&NVIC_InitStruct);
	NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority =1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

	NVIC_Init(&NVIC_InitStruct);

  1. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    这行代码设置了中断优先级分组为第2组。在STM32中,中断优先级分为抢占优先级和响应优先级,通过NVIC_PriorityGroupConfig函数可以配置这两类优先级的比例。第2组表示有2位用于抢占优先级,2位用于响应优先级,总共支持4个抢占优先级和4个响应优先级。

  2. NVIC_InitTypeDef NVIC_InitStruct;
    定义了一个NVIC_InitTypeDef类型的结构体变量NVIC_InitStruct,用于存储中断通道的初始化配置信息。

3-7行和10-14行分别配置了两个中断通道:

  • 对于EXTI4_IRQn:

    • NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;:指定了中断通道为EXTI4的中断。
    • NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;:设置抢占优先级为2。
    • NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;:设置响应优先级为1。
    • NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;:使能该中断通道。
  • 对于EXTI9_5_IRQn:

    • 类似地,这里配置了EXTI9_5(这是一个组合中断,代表EXTI5到EXTI9的中断)的中断通道,抢占优先级设置为1,响应优先级也是1,并且也使能了这个中断通道。

8行和15行:

  • NVIC_Init(&NVIC_InitStruct);:调用NVIC_Init函数,将NVIC_InitStruct结构体中的配置应用到相应的中断通道上。第一次调用是为EXTI4,第二次调用是为EXTI9_5。

现在来分析两个中断(EXTI4和EXTI9_5)的响应顺序:

  • EXTI4_IRQn 的配置是:抢占优先级为2,响应优先级为1。
  • EXTI9_5_IRQn 的配置是:抢占优先级为1,响应优先级也为1。

在STM32的中断优先级管理中,当一个中断发生时,处理器首先比较它们的抢占优先级。具有较高抢占优先级的中断可以打断正在处理的较低抢占优先级中断。如果抢占优先级相同,则会比较响应优先级,响应优先级较高的中断会优先得到服务,前提是当前没有更高抢占优先级的中断在处理中。

根据上述规则,EXTI9_5_IRQn 的抢占优先级为1,而 EXTI4_IRQn 的抢占优先级为2。因此,如果这两个中断同时请求服务,EXTI9_5_IRQn 会优先响应,因为它具有更高的抢占优先级,即使它的响应优先级与EXTI4相同。只有在EXTI9_5_IRQn被完全服务完毕或者被挂起(如果有更高抢占优先级的中断插入)后,才会轮到EXTI4_IRQn被处理。

5.编写中断服务函数

注意:STM32的中断服务函数遵循特定的命名约定,这些函数名通常在启动文件。

void EXTI4_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line4) == SET)
	{
		EXTI_ClearITPendingBit(EXTI_Line4);
		for(uint8_t i = 0 ; i < 5 ; i++)
		{
			LED_FLASH();
		}
		
	}
}
void EXTI9_5_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line5) == SET)
	{
		EXTI_ClearITPendingBit(EXTI_Line5);
		for(uint8_t i = 0 ; i < 5 ; i++)
		{
			LED3_FLASH();
		}
	}
}

1.EXTI4_IRQHandler

这是EXTI4中断的处理函数。当中断控制器检测到EXTI线4上有中断请求时,会调用此函数。

  1. if(EXTI_GetITStatus(EXTI_Line4) == SET)
    这行代码检查EXTI线4是否确实产生了中断(即中断挂起位是否为SET)。EXTI_GetITStatus函数用于获取指定EXTI线的中断挂起状态。

  2. EXTI_ClearITPendingBit(EXTI_Line4);
    如果EXTI4确实产生了中断,这行代码会清除该中断的挂起位,防止中断标志一直存在,导致函数被重复调用。

  3. for(uint8_t i = 0 ; i < 5 ; i++) { LED_FLASH(); }
    进行一个循环,调用LED_FLASH()函数5次。这通常是用于示例目的,表示当EXTI4中断发生时,执行操作,让LED闪烁5次。

2.EXTI9_5_IRQHandler

这是EXTI9_5中断的处理函数,它实际上是一个组合中断,覆盖了EXTI5到EXTI9。但在这个示例中,只针对EXTI线5进行了处理。

  1. if(EXTI_GetITStatus(EXTI_Line5) == SET)
    检查EXTI线5是否有中断挂起,即是否真的发生了中断。

  2. EXTI_ClearITPendingBit(EXTI_Line5);
    清除EXTI线5的中断挂起位,确保中断被妥善处理后不会再次误触发。

  3. for(uint8_t i = 0 ; i < 5 ; i++) { LED3_FLASH(); }
    同样地,执行一个循环,调用LED3_FLASH()函数5次,这里的操作是控制第三个LED闪烁,以区别于EXTI4的中断响应。

根据之前的配置信息,EXTI9_5_IRQHandler中断的抢占优先级被设置为了1,而EXTI4_IRQHandler中断的抢占优先级为2。在STM32的中断管理系统中,具有更高抢占优先级的中断可以打断正在执行的较低抢占优先级中断。

EXTI9_5_IRQHandler中断在EXTI4_IRQHandler中断服务例程执行过程中触发,由于EXTI9_5_IRQHandler具有更高的抢占优先级,它将能够打断EXTI4_IRQHandler的执行,处理器会先完成EXTI9_5_IRQHandler的处理,然后再返回继续执行(或重新开始,如果必要的话)EXTI4_IRQHandler

这种中断嵌套处理机制确保了高优先级的中断能够得到及时响应,从而满足实时系统中的紧急处理需求。

6.完整程序

#include "bsp_exti.h"
#include "bsp_key.h"


void EXTI_KEY_Config(void)
{
	RCC_APB2PeriphClockCmd(KEY_GPIO_CLK, ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin = KEY1_GPIO_PIN|KEY2_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct);
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource4);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource5);
	EXTI_InitTypeDef EXTI_InitStruct;
	EXTI_InitStruct.EXTI_Line = EXTI_Line4 | EXTI_Line5;
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;
	
	EXTI_Init(&EXTI_InitStruct);
}

void EXTI_NVIC_Config(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority =1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

	NVIC_Init(&NVIC_InitStruct);
	NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority =1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

	NVIC_Init(&NVIC_InitStruct);
}
  • 32
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FightingLod

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值