STM32F103VET6之中断详解

目录

前言

一、中断和事件的区别

二、功能框图

三、外部中断配置

四、源码分析

总结


前言

        在嵌入式系统的世界里,微控制器(MCU)是许多应用的核心。要想充分发挥芯片的性能,熟练掌握其中断管理系统是必不可少的。中断,作为MCU与外界交互的重要手段,能够让我们的程序实时响应外部事件,从而提高系统的整体效率和实时性。

        在本文中,我们将深入探讨STM32F103VET6的中断系统,包括其基本概念、工作原理、配置方法以及实际应用案例。通过本文的学习,将能够理解和掌握STM32F103VET6的中断管理,提升你的嵌入式系统设计能力。


一、中断和事件的区别

        中断:中断是指当CPU执行程序时,由于发生了某种随机的事件(外部或内部),引起CPU暂时中断正在运行的程序,转去执行一段特殊的服务程序(中断服务子程序或中断处理程序),以处理该事件,该事件处理完后又返回被中断的程序继续执行。其中,引发中断的随机事件被称为中断源。

        事件:在嵌入式系统中,事件本质上就是一个触发信号,用来触发特定的外设模块或核心本身(唤醒),它可以触发中断,也可以不触发。事件只是一个触发信号(脉冲),用于表示某个特定的条件已经发生或满足。事件通常与中断相关,但二者在概念和处理方式上有所区别。

        二者的区别:用一个简单的例子来说明,想象你正在看一本有趣的书,突然电话铃响了。在这个例子中,电话铃响就是一个事件,它打断了你的阅读,需要你做出响应。你可以选择忽略电话继续阅读,也可以选择接听电话。如果你选择接听电话,那么你就响应了这个事件。这个过程类似于中断的处理。中断是一种特殊的事件,当它发生时,系统会立即停止当前的任务,转而处理这个中断。就像你选择接听电话一样,系统会跳转到专门的中断处理程序来处理这个中断。

        中断处理程序就像是一个专门用来接听电话的房间。一旦你进入这个房间,你就不能再继续阅读了,而是专注于电话通话。同样地,当中断处理程序运行时,它会暂停其他任务的执行,专注于处理中断。

        所以事件只是表明发生了某件事情,而中断则需要系统立即响应。在这个例子中,电话铃响是一个事件,你可以选择忽略它;但是当中断发生时,系统必须立即响应,不能选择忽略。可以把事件看作是一个提醒或者通知,而中断则是一种需要立即行动的紧急情况。在嵌入式系统中,这种紧急情况可能是外部设备发出的请求、传感器的信号等等,需要系统立即处理以确保系统的实时性和稳定性。

二、功能框图

        外部中断EXTI:外部中断是指当某种外部事件发生时,单片机的中断系统将迫使CPU暂停正在执行的程序,转而去进行中断事件的处理。例如,在单片机实时地处理外部事件时,如果某个外部事件发生(如按键输入、定时器溢出等),系统会暂停当前执行的程序,跳转到相应的中断处理程序去处理这个外部事件。处理完中断事件后,程序会返回到被中断的位置继续执行。

STM32F103VET6之中断详解_第1张图片

        由外部中断/事件控制器框图能看出,主要分为两大功能,控制中断和事件的产生。图中的双向箭头上的斜杠,旁边标注20代表有20个线路,可以将其配置成中断源或者事件源,但是通道19只适用于互联型产品,对于非互联型产品只有19个线路。

        首先来看中断时如何产生的,从右至左看,首先时输入线,对于非互联型产品,有19个中断/事件输入线,可以通过配置任意一个可用的GPIO作为输入线,输入线确定之后,来到边沿检测电路,可以通过配置相关寄存器来选择上升沿触发、下降沿触发或者上升/下降均触发;然后来到一个或门电路,一端来自边沿检测电路,另一端来自软件中断事件寄存器。那什么是软件中断呢?

        软件中断:软件中断是一种由软件程序触发的中断,通常用于系统调用、进程切换、异常处理等任务。与硬件中断不同,软件中断不是由硬件设备触发的,而是由软件程序主动发起的。软件中断需要在程序中进行调用,其响应速度和实时性相对较差,但是具有灵活性。

        举个例子,假如你正在家里看电视,突然电话铃响了。

        硬件中断就像是电话铃响,它是由电话设备(硬件设备)自动触发的,打断了你正在做的事情(看电视),需要你立即响应(接电话)。电话铃响的优先级是很高的,因为它是一个紧急的事件,需要你立即处理。

        软件中断就像是你想换一个电视频道,这是你自己主动发起的一个请求(软件触发),你需要按下遥控器上的按钮(执行相应的代码),然后电视才会切换到新的频道(处理中断事件)。这个过程的优先级相对较低,因为它不是紧急事件,你可以在任何时候进行频道切换,而且即使稍微延迟一下也不会造成太大的影响。

        所以,硬件中断和软件中断的主要区别在于触发方式(自动触发还是主动发起)、优先级(紧急程度)和实时性(处理速度)。

        回到框图中,或门电路任意一端有1就输出1,所以当检测到外部线路有电平变化时,就输出1,紧接着来到中断挂起寄存器,当在外部中断线上发生了选择的边沿事件,该位被置’1’。将中断屏蔽器配置为开放来自外部线路上的中断请求,两端都置位时,与门电路输出1,最后输出到NVIC中,实现中断的产生,在中断服务函数中执行相应的操作。

        事件的产生也是同样的原理,通过边沿检测电路检测外部线路电平变化,经过或门电路输出,将事件屏蔽器置位,开放来自外部线路的事件请求,最后输出到脉冲发生器进行控制。

三、外部中断配置

外部中断的配置总结为以下几个步骤:

  1. 配置需要产生的中断的GPIO
  2. 开启AFIO的时钟,将对应GPIO重映射到相应的外部中断信号源
  3. 配置外部中断的触发模式
  4. 配置中断分组
  5. 编写中断服务函数
  6. 清除中断标志位

这里所提及的AFIO是什么呢?

        在STM32中,AFIO(Advanced Function Input/Output)是指GPIO端口的复用功能。GPIO除了用作普通的输入输出(主功能)外,还可以作为片上外设的复用输入输出,如串口、ADC等,这些就是复用功能。大多数GPIO都有一个默认复用功能,有的GPIO还有重映射功能。重映射功能是指把原来属于A引脚的默认复用功能转移到了B引脚进行使用,前提是B引脚具有这个重映射功能。当把GPIO用作EXTI外部中断或使用重映射功能的时候,必须开启AFIO时钟。而在使用默认复用功能的时候,则不必开启AFIO时钟。

四、源码分析

exti.c

#include "exti.h"

static void KEY1_NVIC_Config()
{
		//配置中断分组为第2组
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
		//配置中断源、抢占优先级、子优先级
        NVIC_InitTypeDef NVIC_InitStructure;
		NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	
		NVIC_Init(&NVIC_InitStructure);	
}

void KEY_EXTI_Cogfig()
{
		// 使能GPIOA时钟、AFIO时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
			
		GPIO_InitTypeDef GPIO_InitStructure;
		//PA0配置
		GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
		GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
		GPIO_Init(GPIOA, &GPIO_InitStructure);

		//配置PA0重映射到外部中断输入线
        GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
	
        //配置外部中断的通道,触发方式
		EXTI_InitTypeDef EXTI_InitStructure;
		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);

        //配置NVIC分组
        KEY1_NVIC_Config();		

}

        首先来看外部中断配置函数KEY_EXTI_Cogfig(),先打开GPIOA和AFIO的时钟,因为配置GPIO重映射需要用到外部中断配置寄存器 1(AFIO_EXTICR1),所以必须打开AFIO的时钟;然后将对应GPIO端口配置成浮空输入,端口电平的到底取决于外部设备输入的电平,然后调用GPIO_EXTILineConfig()函数将PA0重映射到外部中断输入线,右键跳转到定义处。


void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
{
  uint32_t tmp = 0x00;
  /* Check the parameters */
  assert_param(IS_GPIO_EXTI_PORT_SOURCE(GPIO_PortSource));
  assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource));
  
  tmp = ((uint32_t)0x0F) << (0x04 * (GPIO_PinSource & (uint8_t)0x03));
  AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;
  AFIO->EXTICR[GPIO_PinSource >> 0x02] |= (((uint32_t)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (uint8_t)0x03)));
}

        在此函数内部,首先会进行参数检查。如果GPIO_PortSourceGPIO_PinSource符合要求,则会继续执行配置操作。函数会使用位运算和位移操作来设置AFIO->EXTICR寄存器。这个寄存器用于配置每个外部中断线路的源。我们配置PA0引脚作为外部中断输入,并选择TIM3作为触发源。

具体来说,函数会执行以下操作:

  1. 计算要操作的位数(由GPIO_PinSource & (uint8_t)0x03确定)。PA0引脚对应的是第0位。
  2. 将0x0F左移对应的位数,得到一个掩码,用于设置相应的位。
  3. 清除该位的原有值(使用按位与操作符)。
  4. 将GPIO的端口源(例如GPIOA)左移对应的位数,得到一个新的值。
  5. 将新的值与清除原有值的掩码进行按位或操作,将新的值写入对应的位。

        这样,通过一系列的位操作和位移运算,AFIO->EXTICR寄存器就被配置为预期的值,将PA0引脚映射到外部中断输入。

接下来,通过给EXTI_InitStructure的成员变量赋值,我们设置了以下参数:

  1. EXTI_Line:设置为EXTI_Line0,表示我们要配置的是Line0的中断线。
  2. EXTI_Mode:设置为EXTI_Mode_Interrupt,表示我们希望将该中断线配置为中断模式。
  3. EXTI_Trigger:设置为EXTI_Trigger_Rising,表示我们希望配置为上升沿触发。
  4. EXTI_LineCmd:设置为ENABLE,表示我们希望使能该中断线。

然后,我们调用EXTI_Init()函数来初始化外部中断控制器。这个函数会根据传入的参数来配置对应的硬件寄存器。

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)
{
  uint32_t tmp = 0;

  /* Check the parameters */
  assert_param(IS_EXTI_MODE(EXTI_InitStruct->EXTI_Mode));
  assert_param(IS_EXTI_TRIGGER(EXTI_InitStruct->EXTI_Trigger));
  assert_param(IS_EXTI_LINE(EXTI_InitStruct->EXTI_Line));  
  assert_param(IS_FUNCTIONAL_STATE(EXTI_InitStruct->EXTI_LineCmd));

  tmp = (uint32_t)EXTI_BASE;
     
  if (EXTI_InitStruct->EXTI_LineCmd != DISABLE)
  {
    /* Clear EXTI line configuration */
    EXTI->IMR &= ~EXTI_InitStruct->EXTI_Line;
    EXTI->EMR &= ~EXTI_InitStruct->EXTI_Line;
    
    tmp += EXTI_InitStruct->EXTI_Mode;

    *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;

    /* Clear Rising Falling edge configuration */
    EXTI->RTSR &= ~EXTI_InitStruct->EXTI_Line;
    EXTI->FTSR &= ~EXTI_InitStruct->EXTI_Line;
    
    /* Select the trigger for the selected external interrupts */
    if (EXTI_InitStruct->EXTI_Trigger == EXTI_Trigger_Rising_Falling)
    {
      /* Rising Falling edge */
      EXTI->RTSR |= EXTI_InitStruct->EXTI_Line;
      EXTI->FTSR |= EXTI_InitStruct->EXTI_Line;
    }
    else
    {
      tmp = (uint32_t)EXTI_BASE;
      tmp += EXTI_InitStruct->EXTI_Trigger;

      *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    }
  }
  else
  {
    tmp += EXTI_InitStruct->EXTI_Mode;

    /* Disable the selected external lines */
    *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
  }
}

具体来说,此函数可能会执行以下操作:

  1. 首先检查参数合法性;
  2. 判断是否使能中断,若使能中断就将参数写入对应寄存器
  3. 通过读取EXTI_InitStructure.EXTI_Line来决定要配置哪个中断线(在这里是Line0);
  4. 将IMR(屏蔽寄存器)和EMR(事件选择寄存器)中的对应位清零,以清除之前的任何现有配置;
  5. 根据EXTI_InitStructure.EXTI_Mode的值(在这里是EXTI_Mode_Interrupt),设置IMR和EMR中对应的中断线位,以将该中断线配置为中断模式;
  6. 根据EXTI_InitStructure.EXTI_Trigger的值(在这里是EXTI_Trigger_Rising),设置RTSR(上升沿触发选择寄存器)和FTSR(下降沿触发选择寄存器)中对应的中断线位,以将该中断线配置为上升沿触发。

        这些操作都是直接针对STM32的硬件寄存器进行的,通过位操作来设置或清除特定的位,从而实现对外部中断控制器的配置。

然后配置NVIC的分组,这里设置为2,意味着2位抢占优先级和2位响应优先级。然后,创建了一个NVIC_InitTypeDef类型的结构体变量NVIC_InitStructure,并设置了以下属性:

  • NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;:指定了这个中断通道是EXTI0。
  • NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;:设置了抢占优先级为1。抢占优先级高的中断可以打断正在处理的抢占优先级低的中断。
  • NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;:设置了响应优先级为1。
  • NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;:使能了这个中断通道。

最后,通过NVIC_Init(&NVIC_InitStructure)把以上配置应用到NVIC。

static void KEY1_NVIC_Config()
{
		NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
		NVIC_InitTypeDef NVIC_InitStructure;
		NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	
		NVIC_Init(&NVIC_InitStructure);
	
}

我这里只配置了一个外部中断源,对于中断的分组可以不那么严谨,如果当中断源较多时,我们就需要合理分配优先级来管理中断,具体来说主要遵循

  1. 重要优先:对于重要或紧急的中断,应该将其优先级设置为较高,以确保系统能够及时响应和处理这些中断。这样可以避免重要任务被其他中断干扰或延迟。
  2. 避免抢占:如果某个中断需要执行一些耗时的操作,应该将其优先级设置为较低,以避免它抢占了其他中断的处理时间。这样可以确保其他中断能够得到及时响应和处理。
  3. 平衡性:在配置NVIC分组时,应该考虑各个中断源之间的平衡性。如果某些中断总是同时发生或需要协调处理,可以将它们的优先级设置为相同或相近的水平。这样可以避免某些中断被其他中断长时间地打断或延迟。

        上述一整个流程就完成了外部中断的配置,当外部中断产生时,系统就会停下目前正在处理的事件,转而去执行中断服务函数,执行完成之后再回到主程序继续执行之前搁置的事件。

exti.h

#ifndef __EXTI_H
#define __EXTI_H

#include "stm32f10x.h"

static void KEY1_NVIC_Config(void);
void KEY_EXTI_Cogfig(void);

#endif

中断服务函数

void EXTI0_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line0)!=RESET)
	{
        LED_ON;
		EXTI_ClearFlag(EXTI_Line0);
	}
	
}

main.c

int main(void)
{	
    LED_Init();//LED初始化
	KEY_EXTI_Cogfig();//配置外部中断
	while(1)
	{
			
	}
}

总结

        总而言之,我们需要理解STM32F103VET6的中断系统如何工作,包括中断源,中断优先级,中断处理流程等基本概念。这有助于我们确定何时以及如何使用中断,以最大限度地提高微控制器的效率和性能。其次,我们需要根据实际应用的需求来配置和使用中断系统。例如,我们可以配置不同的中断源和优先级以满足特定的实时性需求,或者使用中断嵌套来处理多个同时发生的中断。我们还可以在中断处理程序中执行特定的任务,例如读取或清除标志位,以控制中断的响应和恢复过程。通过以上知识的学习,我们可以充分利用STM32F103VET6的中断系统来创建高效,实时和可靠的嵌入式应用程序。这不仅可以帮助我们实现复杂的功能,还可以提高系统的性能和响应速度。

你可能感兴趣的:(STM32学习,stm32,嵌入式硬件,单片机)