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