EXTI外部中断

NVIC的名字叫做,嵌套中断向量控制器

NVIC的出现,就是为了当CPU的秘书,如果我们把所有的中断都接在CPU上,CPU对应位置就得进行适配,设计上就很麻烦。并且如果很多中断进行申请,造成了拥堵,CPU也会很难搞,因为CPU还是主要负责进行运算的,中断分配的任务就放到别的地方吧。

NVIC有很多个输入口,你有多少个中断都可以接过来

NVIC只有一个输出口,直接接到CPU上

stm32有68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级

NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级,记住,值越小的优先级越高,比如0就是最高的优先级

抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队

举个例子,就好像是有个医生在给一个人进行看病,抢占优先级高的可以直接把看病的那个人挤开,让自己去看。然后响应优先级就好像是有个人排队,他就应该排在第一位,大概就是这个意思。


综上所述,现在我们可以这么理解,NVIC其实可以理解成为一个叫号系统,而CPU就是那个医生,下面我们来介绍第一位病人,EXTI外部中断


EXTI(Extern Interrupt)外部中断

EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序

支持的触发方式:上升沿/下降沿/双边沿/软件触发

解释一下:这里的上升沿是指由低电平上升到高电平,下降沿是由高电平降低到低电平,而双边沿就是指上下都计数

支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断

通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒

触发响应方式:中断响应/事件响应

首先介绍一下EXTI的基本结构

这里的每个GPIO都有16个引脚,显然对于只有16个输入口的EXT是远远超出的,所以我们中间加一个AFIO中断引脚选择器,你可以理解为GPIOA,B,C共用一个Pin引脚口,那么就可以解释了,为什么相同的引脚不能同时触发中断,因为这样,你根本不知道是哪个GPIO口发生了中断。

经过EXTI后,分为了两种输出,一种直接接到NVIC,一种接入其他外设。

根据常理,本来20种的中断输入应该有20种的中断输出,但是可能是STM公司觉得有点占用NVIC的通道,于是,将EXTI59, EXTI1015分别合并为了一种通道, 也就是说EXTI5~9会触发同一种中断函数,同理10到15也是

对于合并通道的中断函数,还需根据标志位进一步确定这个是由哪个中断函数进来的

AFIO复用IO口

AFIO主要用于引脚复用功能的选择和重定义,在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择。

这就是AFIO的作用

EXTI外部中断的框图

记住其中带弧度的是或门,只要有一个输入端是1,那最终输出端的数值就是1

平的是与门,记住电路的符号就行

这也就能解释为什么软件的外部中断,也可以进行中断

由于自身设计的微妙,我们可以做到A,B两个端口输出的波的相位差90度,可以通过判断A,B端口的高低电平,从而确定此时是正转还是反转。

分析A端,当左端的端口并未进行接通时,由于VCC连接R1,相当于进行电位的上拉,从此A点的电位为高电位。而当左端的端口接上时A端相当于直接接GND。这里的R3起到了限流电阻的作用,防止电流过大。

这里的C1起到了滤波电容的作用,防止输出的电位过于抖动

滤波电容这块,感觉还是不太会

右端的电路同理

程序部分

首先在Hardware文件夹中新建CountSensor.h和CountSensor.c文件

CountSensor.h文件
    #ifndef _COUNT_SENSOR_H
    #define _COUNT_SENSOR_H

    void CountSensor_Init(void);
    uint16_t get(void);

    #endif

那么如何配置外部的中断

只需要把从GPIO到NVIC这一路的信号电路都打开即可

第一步,配置RCC,把我们这里涉及到的外设时钟都打开,不打开时钟,外设是无法工作的

第二步,配置GPIO口,选择我们的端口为输入模式

第三步,配置AFIO,选择我们用的这一路GPIO,连接到后面的EXTI

第四步,配置EXTI,选择边沿触发模式,不如上升沿,下降沿,或者双边沿

第五步,配置NVIC,给我们这个中断一个合适的优先级别

最后通过NVIC,外部中断的信号就能进入CPU了,这样,CPU才能收到中断信号来跳转到中断函数里面,执行中断程序

那,这五步就是外部中断的配置流程

1.首先第一步,配置时钟RCC

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

注意是RCC_APB2因为GPIOB是APB2的外设

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

当你不确定这个外设是接在哪个总线上的时候,我们可以跳转到这个函数的定义

比如这个函数RCC_APB2这个函数,可以通过arg看到所有的这些外设类型

/**
  * @brief  Enables or disables the High Speed APB (APB2) peripheral clock.
  * @param  RCC_APB2Periph: specifies the APB2 peripheral to gates its clock.
  *   This parameter can be any combination of the following values:
  *     @arg RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB,
  *          RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE,
  *          RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1,
  *          RCC_APB2Periph_ADC2, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1,
  *          RCC_APB2Periph_TIM8, RCC_APB2Periph_USART1, RCC_APB2Periph_ADC3,
  *          RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17,
  *          RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM11     
  * @param  NewState: new state of the specified peripheral clock.
  *   This parameter can be: ENABLE or DISABLE.
  * @retval None
  */

接着还需要打开EXTI和NVIC的外设,但是这两个外设的时钟是一直打开的,所以不需要再次打开,

NVIC不需要开启时钟的原因是因为,NVIC属于内核的外设,内核的外设都是不需要开启时钟的

而RCC管的都是内核外的外设,所以管不到NVIC

第二步,配置GPIO

GPIO_InitTypeDef GPIO_InitStructure;//注释,这里的GPIO_InitStructure只是一个名称,也可以替换成为u,等等其他的名字
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//这个输入模式,具体需要什么,需要到stm32手册中进行查询
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);

接下来进行第三步,配置AFIO,AFIO并没有自己独立的库函数,它的对应函数在GPIO的文件中

void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

然后在其中进行跳转,可以查到对应的

/**
  * @brief  Selects the GPIO pin used as EXTI Line.
  * @param  GPIO_PortSource: selects the GPIO port to be used as source for EXTI lines.
  *   This parameter can be GPIO_PortSourceGPIOx where x can be (A..G).
  * @param  GPIO_PinSource: specifies the EXTI line to be configured.
  *   This parameter can be GPIO_PinSourcex where x can be (0..15).
  * @retval None
  */
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)));
}

通过最后两行,我们可以发现,其实,这个函数就是配置AFIO的函数

如果你想配置PB14号口为中断口,那么我们就可以这样写

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);

第四步,配置EXTI文件

首先先来学习一下EXTI的基本库函数

void EXTI_DeInit(void);//调用它,就可以把EXTI的配置都清除,恢复成上电默认的状态
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);//可以根据这个结构体里的函数配置EXTI外设,初始化EXTI主要用的就是这个函数,使用方法与GPIO_Init等同对比就行 
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
void EXTI_ClearFlag(uint32_t EXTI_Line);
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

代码书写

我们首先肯定得初始化外设

/**
  * @brief  Initializes the EXTI peripheral according to the specified
  *         parameters in the EXTI_InitStruct.
  * @param  EXTI_InitStruct: pointer to a EXTI_InitTypeDef structure
  *         that contains the configuration information for the EXTI peripheral.
  * @retval None
  */
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)

然后根据提示,进行相关参数的配置

在文档中查找相关词EXTI_InitTypeDef,可以得到如下的代码

    EXTI_InitTypeDef a;
    a.EXTI_Line=EXTI_Line14;//这四项直接在定义完a后会直接出来
    a.EXTI_LineCmd=ENABLE;
    a.EXTI_Mode=EXTI_Mode_Interrupt;
    a.EXTI_Trigger=EXTI_Trigger_Falling;
    EXTI_Init(&a);//初始化a去

在全局寻找EXTI_Line,第一个参数


#define EXTI_Line0       ((uint32_t)0x00001)  /*!< External interrupt line 0 */
#define EXTI_Line1       ((uint32_t)0x00002)  /*!< External interrupt line 1 */
#define EXTI_Line2       ((uint32_t)0x00004)  /*!< External interrupt line 2 */
#define EXTI_Line3       ((uint32_t)0x00008)  /*!< External interrupt line 3 */
#define EXTI_Line4       ((uint32_t)0x00010)  /*!< External interrupt line 4 */
#define EXTI_Line5       ((uint32_t)0x00020)  /*!< External interrupt line 5 */
#define EXTI_Line6       ((uint32_t)0x00040)  /*!< External interrupt line 6 */
#define EXTI_Line7       ((uint32_t)0x00080)  /*!< External interrupt line 7 */
#define EXTI_Line8       ((uint32_t)0x00100)  /*!< External interrupt line 8 */
#define EXTI_Line9       ((uint32_t)0x00200)  /*!< External interrupt line 9 */
#define EXTI_Line10      ((uint32_t)0x00400)  /*!< External interrupt line 10 */
#define EXTI_Line11      ((uint32_t)0x00800)  /*!< External interrupt line 11 */
#define EXTI_Line12      ((uint32_t)0x01000)  /*!< External interrupt line 12 */
#define EXTI_Line13      ((uint32_t)0x02000)  /*!< External interrupt line 13 */
#define EXTI_Line14      ((uint32_t)0x04000)  /*!< External interrupt line 14 */
#define EXTI_Line15      ((uint32_t)0x08000)  /*!< External interrupt line 15 */
#define EXTI_Line16      ((uint32_t)0x10000)  /*!< External interrupt line 16 Connected to the PVD Output */
#define EXTI_Line17      ((uint32_t)0x20000)  /*!< External interrupt line 17 Connected to the RTC Alarm event */
#define EXTI_Line18      ((uint32_t)0x40000)  /*!< External interrupt line 18 Connected to the USB Device/USB OTG FS
                                                   Wakeup from suspend event */                                    
#define EXTI_Line19      ((uint32_t)0x80000)  /*!< External interrupt line 19 Connected to the Ethernet Wakeup event */
                                          

第二个参数

FunctionalState EXTI_LineCmd;     /*!< Specifies the new state of the selected EXTI lines.
                                         This parameter can be set either to ENABLE or DISABLE */ 

开始中断还是不开启,ENABLE还是DISABLE

第三个参数


typedef enum
{
  EXTI_Mode_Interrupt = 0x00,
  EXTI_Mode_Event = 0x04
}EXTIMode_TypeDef;

是外部中断还是事件中断,这里我们采用外部中断

第四个参数

/** 
  * @brief  EXTI Trigger enumeration  
  */

typedef enum
{
  EXTI_Trigger_Rising = 0x08,
  EXTI_Trigger_Falling = 0x0C,  
  EXTI_Trigger_Rising_Falling = 0x10
}EXTITrigger_TypeDef;

这里代表的是选择上升沿触发,下降沿触发,还是上升下降沿都触发

到此为止,外部中断配置完成,第四步结束

    EXTI_InitTypeDef a;
    a.EXTI_Line=EXTI_Line14;
    a.EXTI_LineCmd=ENABLE;
    a.EXTI_Mode=EXTI_Mode_Interrupt;
    a.EXTI_Trigger=EXTI_Trigger_Falling;
    EXTI_Init(&a);
第五步,配置NVIC外设

因为NVIC属于stm32内核,所以其库函数,被分配到杂项misc中

先学习一下NVIC的库函数

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);

这个函数用来配置中断分组的,参数是中断分组的方式

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);

根据NVIC结构体中指定的参数初始化NVIC

跳转

NVIC_PriorityGroupConfig

查到

/**
  * @brief  Configures the priority grouping: pre-emption priority and subpriority.
  * @param  NVIC_PriorityGroup: specifies the priority grouping bits length. 
  *   This parameter can be one of the following values:
  *     @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority
  *                                4 bits for subpriority
  *     @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
  *                                3 bits for subpriority
  *     @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
  *                                2 bits for subpriority
  *     @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
  *                                1 bits for subpriority
  *     @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
  *                                0 bits for subpriority
  * @retval None
  */
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
  /* Check the parameters */
  assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
  
  /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
  SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

//pre-emption priority代表的是抢占优先级,subpriority代表的是相应优先级

这里我们选择第二组,两位响应,两位抢占,稍微平均一点

即配置成

void NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

然后就是经典操作了

对NVIC的初始化

    NVIC_InitTypeDef b;
    b.NVIC_IRQChannel= ;
    b.NVIC_IRQChannelCmd= ;
    b.NVIC_IRQChannelPreemptionPriority= ;
    b.NVIC_IRQChannelSubPriority=  ;
    NVIC_Init(&b);

接下来就是配置参数

跳转定义,全局工程文件搜索IRQn_Type

根据我们选择芯片的种类,选择对应的中断函数

#ifdef STM32F10X_MD
  ADC1_2_IRQn                 = 18,     /*!< ADC1 and ADC2 global Interrupt                       */
  USB_HP_CAN1_TX_IRQn         = 19,     /*!< USB Device High Priority or CAN1 TX Interrupts       */
  USB_LP_CAN1_RX0_IRQn        = 20,     /*!< USB Device Low Priority or CAN1 RX0 Interrupts       */
  CAN1_RX1_IRQn               = 21,     /*!< CAN1 RX1 Interrupt                                   */
  CAN1_SCE_IRQn               = 22,     /*!< CAN1 SCE Interrupt                                   */
  EXTI9_5_IRQn                = 23,     /*!< External Line[9:5] Interrupts                        */
  TIM1_BRK_IRQn               = 24,     /*!< TIM1 Break Interrupt                                 */
  TIM1_UP_IRQn                = 25,     /*!< TIM1 Update Interrupt                                */
  TIM1_TRG_COM_IRQn           = 26,     /*!< TIM1 Trigger and Commutation Interrupt               */
  TIM1_CC_IRQn                = 27,     /*!< TIM1 Capture Compare Interrupt                       */
  TIM2_IRQn                   = 28,     /*!< TIM2 global Interrupt                                */
  TIM3_IRQn                   = 29,     /*!< TIM3 global Interrupt                                */
  TIM4_IRQn                   = 30,     /*!< TIM4 global Interrupt                                */
  I2C1_EV_IRQn                = 31,     /*!< I2C1 Event Interrupt                                 */
  I2C1_ER_IRQn                = 32,     /*!< I2C1 Error Interrupt                                 */
  I2C2_EV_IRQn                = 33,     /*!< I2C2 Event Interrupt                                 */
  I2C2_ER_IRQn                = 34,     /*!< I2C2 Error Interrupt                                 */
  SPI1_IRQn                   = 35,     /*!< SPI1 global Interrupt                                */
  SPI2_IRQn                   = 36,     /*!< SPI2 global Interrupt                                */
  USART1_IRQn                 = 37,     /*!< USART1 global Interrupt                              */
  USART2_IRQn                 = 38,     /*!< USART2 global Interrupt                              */
  USART3_IRQn                 = 39,     /*!< USART3 global Interrupt                              */
  EXTI15_10_IRQn              = 40,     /*!< External Line[15:10] Interrupts                      */
  RTCAlarm_IRQn               = 41,     /*!< RTC Alarm through EXTI Line Interrupt                */
  USBWakeUp_IRQn              = 42      /*!< USB Device WakeUp from suspend through EXTI Line Interrupt */  
#endif /* STM32F10X_MD */  

配置第一个参数

b.NVIC_IRQChannel=EXTI15_10_IRQn ;

配置第二个参数

FunctionalState NVIC_IRQChannelCmd;         /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannel
                                                   will be enabled or disabled. 
                                                   This parameter can be set either to ENABLE or DISABLE */ 

负责确定中断通道是使能还是失能,这里我们选择ENABLE

配置第三个参数

NVIC_IRQChannelPreemptionPriority
这个是负责的抢占优先级
NVIC_IRQChannelSubPriority
    这个是负责相应优先级

对于当前我们的这种情况,因为只有一种中断,所以我们采用参数可以 随便一点,只有当有很多个中断的时候,才会出现拥挤

    b.NVIC_IRQChannelPreemptionPriority=1 ;
    b.NVIC_IRQChannelSubPriority= 1 ;

高电平(GPIO_PIN_SET)、低电平(GPIO_PIN_RESET)。

那么中断程序应该放在哪里呢,这就需要我们写一个中断函数

在STM32中,中断函数的名称都是固定的,每个中断通道都对应一个中断函数

中断函数的名字,我们可以参照一下启动文件,在其中找到中断函数

EXTI15_10_IRQHandler

注意,这个中断函数一定要书写正确,因为如果中断函数写错了,那么程序就无法进入中断了

void EXTI15_10_IRQHandler(void)
{
        if(EXTI_GetITStatus(EXTI_Line14)==1)//获取一下是哪个中断置为1了
        {
            EXTI_ClearITPendingBit(EXTI_Line14);//这里需要清除一下中断,因为如果中断函数不清除的话,会一直在中断函数里面循环。如果你不清楚中断标志位,那么它就会一直申请中断。这样程序就会不断相应中断,执行中断程序,那么程序就会卡死在中断程序里面了,所以我们每次中断程序结束后,都应该清除一下标志位
            num++;
        }		
    
}	
更新于

请我喝[茶]~( ̄▽ ̄)~*

bangdexuanyuan 微信支付

微信支付