系列文章目录
STM32中断系统之EXTI外部中断
文章目录
- 系列文章目录
- 前言
- 一、应用案例简介
- 二、电路接线图
- 三、应用案例代码
- 四、应用案例分析
- 4.1 配置外部中断
- 4.1.1 配置RCC时钟
- 4.1.2 配置GPIO
- 4.1.3 配置AFIO
- 4.1.4 配置EXTI
- 4.1.5 配置NVIC
- 4.2 编写中断函数
前言
提示:本文主要用作在学习江科大自化协STM32入门教程后做的归纳总结笔记,旨在学习记录,如有侵权请联系作者
本文主要探讨利用EXTI外部中断实现一个对射式红外传感器计次的功能。
一、应用案例简介
本案例利用EXTI外部中断实现了一个对射式红外传感器计次的功能。当使用挡光片挡在红外对管的中间,此时输出指示灯熄灭(输出高电平),然后再拿开挡光片,此时输出指示灯亮了(输出低电平)。在灭到亮之间会产生一个下降沿,这个下降沿触发单片机引脚的外部中断,然后执行数字加1的中断程序。如此类推,实现了对挡光次数的统计并显示在OLED显示屏上。
二、电路接线图
对射式红外传感器模块VCC、GND分别接电源正负极,DO数字输出端接到PB14口。当使用挡光片在这个对射式红外对管中间经过时,DO口就会输出电平变化的信号,这个电平跳变的信号就会触发STM32 PB14号口的中断,这就是对射式红外传感器计次的原理。
三、应用案例代码
传感器头文件CountSensor.h:
#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_H
void CountSensor_Init(void);
uint16_t CountSensor_Get(void);
#endif
传感器实现文件CountSensor.c:
#include "stm32f10x.h" // Device header
uint16_t CountSensor_Count;
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
uint16_t CountSensor_Get(void)
{
return CountSensor_Count;
}
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line14) == SET)
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
{
CountSensor_Count ++;
}
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
主程序文件main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
int main(void)
{
OLED_Init();
CountSensor_Init();
OLED_ShowString(1, 1, "Count:");
while (1)
{
OLED_ShowNum(1, 7, CountSensor_Get(), 5);
}
}
四、应用案例分析
4.1 配置外部中断
首先,在CountSensor_Init函数里完成外部中断的初始化配置。那要如何配置外部中断呢?我们可以看一下下面这个外部中断整体结构图。简单来说,我们只需要把这个外部中断从GPIO到NVIC这一路中出现的外设模块都配置好,把这条信号电路给打通就行了。
外部中断配置步骤如下:
1. 配置RCC: 把涉及的外设的时钟都打开。
2. 配置GPIO: 选择端口为输入模式。
3. 配置AFIO: 选择我们用的这一路GPIO并将它连接到后面的EXTI。
4. 配置EXTI: 选择边沿触发方式,比如上升沿、下降沿或者双边沿。选择触发响应方式,可以选择中断响应或事件响应,当然我们一般都是中断响应。
5. 配置NVIC: 给中断选择一个合适的优先级
最后,通过NVIC,外部中断信号就能进入CPU了,这样CPU才能收到中断信号,才能跳转到中断函数里执行中断程序,那接下来让我们一个一个来分析。
首先第一步,配置RCC时钟。
4.1.1 配置RCC时钟
- 调用RCC_APB2PeriphClockCmd函数分别开启GPIOB和AFIO(AFIO也是APB2的外设)的时钟。
- 接着还有EXTI和NVIC两个外设的时钟,这两个外设的时钟是一直都打开着的,所以不需要我们再开启时钟了。
注意:EXTI作为一个独立的外设,按理来说应该是需要开启时钟的,但是寄存器里面却没有EXTI时钟的控制位,这个原因手册里也没有找到,网上也没有确切的答案。可能是和EXTI唤醒有关,或者是其他一些电路上设计的考虑,那我们暂时就不用管了,知道它不需要开启时钟就行了。另外NVIC也不需要开启时钟,因为NVIC是内核的外设,内核的外设都是不需要开启时钟的。人家跟CPU一起都是住“皇宫”里的,而RCC管的都是内核外的外设,所以RCC管不着NVIC。
配置RCC时钟代码如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
好,那到这里时钟就配置完了,接着我们就来进行第二步,配置GPIO。
4.1.2 配置GPIO
GPIO的配置在前面GPIO的章节已经讲过了,这里就不再累述了,在这里只讲一下一些需要注意的点。
第一个GPIO_Mode 参数,对于外部中断来说,要选择浮空输入、上拉输入或者下拉输入其中的一个模式。当然,像这种其他外设使用GPIO口的情况,如果你不清楚该配置为什么模式,可以看一下参考手册。找到GPIO这一章,有一个外设的GPIO配置表,里面有写每个外设的各个引脚都需要配置为什么模式。
那在最后,就有EXTI输入线,他给的推荐配置就是浮空、上拉或者下拉。所以在这里,我就给一个GPIO_Mode_IPU,上拉输入,默认为高电平的输入方式。
GPIO初始化配置部分代码如下:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
这样GPIO部分我们就配置好了,然后我们就来进行第三步,配置AFIO。
4.1.3 配置AFIO
简单介绍一下AFIO相关函数。这个AFIO外设,ST公司并没有给它分配专门的库函数文件,它的库函数是和GPIO在一个文件里的,那我们找一下GPIO的.h文件,找到stm32f10x_gpio.h文件,然后拉到最后。
void GPIO_DeInit(GPIO_TypeDef* GPIOx);
void GPIO_AFIODeInit(void);
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_EventOutputCmd(FunctionalState NewState);
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);
首先是 GPIO_AFIODeInit 函数,这个函数是用来复位AFIO外设的,调用一下这个函数,AFIO外设的配置就会全部清除。
然后下面这个 GPIO_PinLockConfig 函数,这个函数是用来锁定GPIO配置的,调用这个函数,参数指定某个引脚,那这个引脚的配置就会被锁定,防止意外更改。这个也是GPIO外设的函数,用的不多,了解一下即可。
然后是 GPIO_EventOutputConfig 和 GPIO_EventOutputCmd 函数,这两个函数是用来配置AFIO的事件输出功能的,用得也不多,了解一下即可。
接下来 GPIO_PinRemapConfig 和 GPIO_EXTILineConfig 函数,这两个函数就比较重要了。
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
GPIO_PinRemapConfig 函数可以用来进行引脚重映射。第一个参数可以选择你要重映射的方式,第二个参数是新的状态。
然后 GPIO_EXTILineConfig 函数就是我们本章节外部中断需要用到的函数了。调用这个函数,就可以配置AFIO的数据选择器,来选择我们想要中断的引脚。
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
那最后一个,GPIO_ETH_MediaInterfaceConfig 函数,这个是和以太网有关的。
先回到这个主线任务上来,我们现在想要配置AFIO外部中断引脚选择,也就是调用 GPIO_EXTILineConfig 函数即可,以下是该函数的定义及说明。
**
* @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)));
}
那上面这里简介写的是,选择GPIO pin作为外部中断线。第一个参数是GPIO_PortSource,选择某个GPIO外设作为外部中断源,这个参数可以是GPIO_PortSourceGPIOx,其中x可以是A到G。我们用的是PB14号引脚,所以就是GPIO_PortSourceGPIOB。第二个参数是GPIO_PinSource,指定要配置的外部中断线,这个参数可以是GPIO_PinSourcex,其中x可以是0到15。所以我们这里就是GPIO_PinSource14,代表连接PB14号口的第14个中断线路。
到这里,AFIO外部中断引脚选择配置就完成了,直接调用 GPIO_EXTILineConfig 函数即可。当执行完这个函数之后,AFIO的第14个数据选择器就拨好了,其中输入端被拨到了GPIOB外设上,对应的就是PB14号引脚,输出端固定连接的是EXTI的第14个中断线路。这样,PB14号引脚的电平信号就可以顺利通过AFIO进入到后级电路EXTI了。
AFIO配置代码如下:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
那接下来,我们就可以进入第四步,配置EXTI了。
4.1.4 配置EXTI
我们先来看一下EXTI的库函数文件,看一下EXTI都有哪些库函数可以用。我们找到stm32f10x_exti.h文件,拖到最后,这些就是EXTI的所有库函数了。
void EXTI_DeInit(void);
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
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);
首先第一个,EXTI_DeInit函数,调用它就可以把EXTI的配置都清除,恢复成上电默认的状态。
然后第二个,EXTI_Init函数,调用这个函数就可以根据这个结构体里的参数配置EXTI外设,我们初始化EXTI主要用到的就是这个函数。
接着第三个,EXTI_StructInit函数,调用这个函数,可以把参数传递的结构体变量赋一个默认值。
那接着看第四个,EXTI_GenerateSWInterrupt函数,这个函数是用来软件触发外部中断的,调用这个函数,参数给一个指定的中断线,就能软件触发一次这个外部中断。
然后剩下的这四个函数也是库函数的模版函数,很多模块都有这四个函数。因为在外设运行的过程中会产生一些状态标志位,比如外部中断来了,是不是会有一个挂起寄存器置了一个标志位?对于其他外设来说,比如串口收到数据会置标志位,定时器时间到也会置标志位。这些标志位都是放在状态寄存器里的,当程序想要获取这些标志位时,就可以用到这四个函数。
其中,这前两个函数。EXTI_GetFlagStatus函数可以获取指定的标志位是否被置1了,EXTI_ClearFlag函数可以对置1的标志位进行清除。那对于这些标志位,有的比较紧急,在置标志位后会触发中断。在中断函数里,如果你想查看标志位和清除标志位那就用下面两个函数。EXTI_GetITStatus函数可以获取中断标志位是否被置1了,EXTI_ClearITPendingBit函数可以清除挂起标志位。
所以总结一下就是,如果你想在主程序里查看和清除标志位,就用前两个函数EXTI_GetFlagStatus和EXTI_ClearFlag,如果你想在中断函数里查看和清除标志位,就用下面这两个函数。其实本质上,这四个函数都是对状态寄存器的读写。上面两个和下面两个是类似的功能,都是读写状态寄存器,只不过是下面这两个函数只能读写与中断有关的标志位,并且对中断是否允许做出了判断,而上面这两个函数只是一般的读写标志位,没有额外的处理,能不能触发中断的标志位都能读取。所以建议在主程序里用上面两个,中断程序里用下面两个。当然你如果非要在中断里用上面两个那其实也是没有问题的,只不过是库函数针对这两种情景,区分了这两类读写函数。
ok,那我们进入正题,完成EXTI的配置调用EXTI_Init函数即可,我们先来看一下该函数的声明。
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)
再来看一下EXTI_InitTypeDef结构体的声明
**
* @brief EXTI Init Structure definition
*/
typedef struct
{
uint32_t EXTI_Line; /*!< Specifies the EXTI lines to be enabled or disabled.
This parameter can be any combination of @ref EXTI_Lines */
EXTIMode_TypeDef EXTI_Mode; /*!< Specifies the mode for the EXTI lines.
This parameter can be a value of @ref EXTIMode_TypeDef */
EXTITrigger_TypeDef EXTI_Trigger; /*!< Specifies the trigger signal active edge for the EXTI lines.
This parameter can be a value of @ref EXTIMode_TypeDef */
FunctionalState EXTI_LineCmd; /*!< Specifies the new state of the selected EXTI lines.
This parameter can be set either to ENABLE or DISABLE */
}EXTI_InitTypeDef;
首先是第一个参数EXTI_Line,这个是指定我们要配置的中断线。那我们需要用PB14所在的第14个线路,所以选择EXTI_Line14。
接下来看第二个参数EXTI_Mode,这个是指定外部中断线的模式。有中断模式和事件模式,那我们这里选中断模式,选择EXTI_Mode_Interrupt。
接着再看第三个参数EXTI_Trigger,这个是指定触发信号的有效边沿。有上升沿触发、下降沿触发和双边沿触发。我这里选一个下降沿触发EXTI_Trigger_Falling。
最后看第四个参数EXTI_LineCmd,这个是指定选择的中断线的新状态。我们选择开启中断,所以选择ENABLE。
EXTI外部中断代码如下:
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
这样我们的外部中断就配置完成了,我们当前的配置是,将EXTI的第14个线路配置为中断模式,下降沿触发,然后开启中断,这样PB14的电平信号就能通过EXTI通向下一级NVIC了。
那最后,我们就来执行第五步,配置NVIC。
4.1.5 配置NVIC
我们还是先看一下库函数文件里的函数,因为NVIC是内核外设,所以它的库函数是被ST发配到杂项这里来了,我们打开misc.h文件,拖到最后,这些就是NVIC相关函数了。
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
我们先来看一下第一个函数NVIC_PriorityGroupConfig,这个函数是用来中断分组的,参数是中断分组的方式。
然后第二个函数NVIC_Init,根据结构体里面指定的参数初始化NVIC。
接着看第三个函数NVIC_SetVectorTable,设置中断向量表。第四个函数NVIC_SystemLPConfig,系统低功耗配置。这两个函数用的不多,了解即可。
在配置中断之前,先指定一下中断的分组然后再使用NVIC_Init函数初始化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;
}
这个简介是配置优先级分组:先占优先级和从占优先级,这里先占优先级就是抢占优先级,从占优先级就是响应优先级,我们这里选择NVIC_PriorityGroup_2。
好,那我们进入最后一步,配置NVIC,先来看一下NVIC_Init函数的声明。
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
NVIC_InitTypeDef结构体声明如下:
**
* @brief NVIC Init Structure definition
*/
typedef struct
{
uint8_t NVIC_IRQChannel; /*!< Specifies the IRQ channel to be enabled or disabled.
This parameter can be a value of @ref IRQn_Type
(For the complete STM32 Devices IRQ Channels list, please
refer to stm32f10x.h file) */
uint8_t NVIC_IRQChannelPreemptionPriority; /*!< Specifies the pre-emption priority for the IRQ channel
specified in NVIC_IRQChannel. This parameter can be a value
between 0 and 15 as described in the table @ref NVIC_Priority_Table */
uint8_t NVIC_IRQChannelSubPriority; /*!< Specifies the subpriority level for the IRQ channel specified
in NVIC_IRQChannel. This parameter can be a value
between 0 and 15 as described in the table @ref NVIC_Priority_Table */
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 */
} NVIC_InitTypeDef;
我们先来看一下第一个参数NVIC_IRQChannel,这个是指定中断通道来开启或关闭,我们来看一下关于这个参数的描述。
/*!< Specifies the IRQ channel to be enabled or disabled.This parameter can be a value of @ref IRQn_Type (For the complete STM32 Devices IRQ Channels list, please refer to stm32f10x.h file) */
这里有个括号,写的是对于完整的STM32中断通道列表,请参考stm32f10x.h文件,这个意思是IRQn_Type的定义不在这个文件,需要到stm32f10x.h文件里找。
可以看到这里有非常多的中断通道,因为这个库函数可以兼容所有的F1系列芯片,但是不同的芯片中断通道列表是不一样的,所以这里有很多条件编译用来选择你使用芯片的中断通道列表。由于我们的芯片是MD中等密度的,所以只需要展开这个MD的条件编译即可。
然后找到这个EXTI15_10_IRQn,STM32的EXTI10到EXTI15都是合并到这个通道里,这样通道就指定好了。
接下来两个参数NVIC_IRQChannelPreemptionPriority、NVIC_IRQChannelSubPriority,就是指定所选通道的抢占优先级和响应优先级了。
我们跳转到它的定义,然后找到这张表,我们在前面选择了分组2 NVIC_PriorityGroup_2,那抢占优先级和响应优先级的取值范围都是0 ~ 3,这里我将抢占优先级和响应优先级都设置为1。
** @defgroup NVIC_Priority_Table
* @{
*/
**
@code
The table below gives the allowed values of the pre-emption priority and subpriority according
to the Priority Grouping configuration performed by NVIC_PriorityGroupConfig function
============================================================================================================================
NVIC_PriorityGroup | NVIC_IRQChannelPreemptionPriority | NVIC_IRQChannelSubPriority | Description
============================================================================================================================
NVIC_PriorityGroup_0 | 0 | 0-15 | 0 bits for pre-emption priority
| | | 4 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_1 | 0-1 | 0-7 | 1 bits for pre-emption priority
| | | 3 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_2 | 0-3 | 0-3 | 2 bits for pre-emption priority
| | | 2 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_3 | 0-7 | 0-1 | 3 bits for pre-emption priority
| | | 1 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_4 | 0-15 | 0 | 4 bits for pre-emption priority
| | | 0 bits for subpriority
============================================================================================================================
@endcode
*/
最后一个参数NVIC_IRQChannelCmd,指定中断通道是使能还是失能,我们选择ENABLE。
NVIC配置代码如下:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
那到这里,整个外部中断的配置也就结束了。外部中断的信号从GPIO到AFIO,再到EXTI,再到NVIC,最终通向CPU,这样才能让CPU由主程序跳转到中断程序里执行。
4.2 编写中断函数
在STM32中,中断函数的名字都是固定的,每个中断通道都对应一个中断函数。中断函数的名字我们可以参考一下启动文件,这里面以IRQHandler结尾的字符串就是中断函数的名字,我们可以找到这个EXTI15_10_IRQHandler这一项,这就是EXTI10到EXTI15的中断函数(中断函数都是无参数无返回值的)。
中断函数代码如下:
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line14) == SET)
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
{
CountSensor_Count ++;
}
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
在中断函数里,一般都是先进行一个中断标志位的判断,确保是我们想要的中断源触发的这个函数。因为这个函数是EXTI10到EXTI15都能进来,所以要先判断一下是不是我们想要的EXTI14进来的。最后,中断结束后一定要再调用一下清除中断标志位的函数,因为只要中断标志位置1了,程序就会跳转到中断函数。如果你不清除中断标志位,那它就会一直申请中断,这样程序就会不断响应中断,执行中断函数,那程序就会卡死在中断函数里了。所以我们每次中断程序结束后,都应该清除一下中断标志位。
对射式红外传感器计次工程代码:EXTI外部中断之对射式红外传感器计次应用案例