1 EXTI介绍
EXTI(External interrupt/event controller):外部 中断/事件 控制器,通常用于微控制器或微处理器上,以便在外部事件发生时触发中断。EXTI的主要目的是允许处理器在需要时执行中断服务程序,以响应外部事件,如按键的按下、松开、传感器状态变化等。
外部事件发生(如GPIO口的电平发生变化) -> EXTI产生中断 -> 内核NVIC。
2 EXIT功能框图讲解
EXTI可以产生中断,也可以产生事件;上图中红色虚线指示的电路流程为产生中断的线路,绿色虚线指示的电路流程为产生事件的线路。
2.1 中断
我们先来看产生中断的线路。
- 1.输入线。
- 输入线总共有20根,如下图所示:
- 通过配置外部中断寄存器x(x=1、2、3、4)来选择外部中断的输入源。
- 2.边沿检测电路。
- 它会根据上升沿触发选择寄存器 (EXTI_RTSR) 和下降沿触发选择寄存器 (EXTI_FTSR) 对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给与门,否则输出无效信号 0。
- 上升沿触发与下降沿触发的区别(以按键来举例):上升沿触发捕捉按键按下的这个脉冲;而下降沿触发捕捉按键松开的这个脉冲。
- 触发类型:可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
- 3.或门。
- 它的输入信号有两个:一个来自于前面 2.边沿检测电路 ,另一个来自于软件中断事件寄存器 (EXTI_SWIER)。 EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线。
-
4.与门。
- 它的输入信号有两个:一个来自于 3.或门 ,另一个来自于中断屏蔽寄存器(EXTI_IMR)。通过这个与门,我们可以采用控制 EXTI_IMR 的方式来实现是否产生中断的目的。与门输出的信号会被保存到挂起寄存器(EXTI_PR)内,如果确定与门输出为1,就会把 EXTI_PR 对应位置 1。
- 5.将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。
2.2 事件
最终输出一个脉冲信号。
-
1.输入线。
-
2.边沿检测电路。
-
3.或门。
-
6.与门。
- 它的输入信号有两个:一个来自于 3.或门 ,另一个来自于事件屏蔽寄存器 (EXTI_EMR)。通过这个与门,我们可以采用控制 EXTI_EMR 的方式来实现是否产生中断的目的。
-
7.脉冲发生器。
- 当 4.与门 的输出信号是一个有效信号1时就会产生一个脉冲,否则就不会产生脉冲。
-
8.一个脉冲信号。
- 这个脉冲信号可以给其他外设电路使用,如触发ADC开始转换、让定时器开始计时等。
2.3 产生中断与产生事件的区别
产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。
产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。
另外,EXTI 是在 APB2 总线上的。
3 EXIT初始化结构体讲解
typedef struct {
uint32_t EXTI_Line; // 中断/事件线
EXTIMode_TypeDef EXTI_Mode; // EXTI 模式
EXTITrigger_TypeDef EXTI_Trigger; // 触发类型
FunctionalState EXTI_LineCmd; // EXTI 使能
}
- 1.EXTI_Line: EXTI 中断/事件线选择,可选 EXTI0 至 EXTI19。
EXTI_Line设置的是EXTI_IMR寄存器,跟真正的输入线是没有关系的(输入线由EXTI_AFIOx设置)。
-
2.EXTI_Mode: EXTI 模式选择,可选为产生中断 (EXTI_Mode_Interrupt) 或者产生事件(EXTI_Mode_Event)。
-
3.EXTI_Trigger: EXTI 边沿触发事件,可选上升沿触发 (EXTI_Trigger_Rising)、下降沿触发 (EXTI_Trigger_Falling) 或者上升沿和下降沿都触发 ( EXTI_Trigger_Rising_Falling)。
-
4.EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线 (ENABLE) 或禁用 (DISABLE)。
EXTI_LineCmd设置的是EXTI_IMR与EXTI_EMR着两个寄存器。
4 GPIO外部中断代码讲解
4.1 实验设计
- 1.PA0、PC13连接到EXTI用于产生中断,PA0、PC13的电平变化通过按键来控制。
- 2.产生一次中断,LED反转一次。
4.2 软件编程
为了使工程更加有条理,我们把EXTI相关的代码独立分开存储,方便以后移植。新建“bsp_exti.c”文件与"bsp_exti.h"文件。
步骤:
- 1.初始化要连接到EXTI的GPIO
- 2.初始化EXTI用于产生中断/事件
- 3.初始化NVIC,用于处理中断
- 4.编写中断服务函数
- 5.main函数
// bsp_exti.h 文件
#ifndef __BSP_EXTI_H
#define __BSP_EXTI_H
#include "stm32f10x.h"
#define KEY1_INT_GPIO_Pin GPIO_Pin_0
#define KEY1_INT_GPIO_PROT GPIOA
#define KEY1_INT_GPIO_CLK RCC_APB2Periph_GPIOA
#define EXTI_KEY1_CLK RCC_APB2Periph_AFIO
#define EXTI_KEY1_GPIO_PortSourceGPIO GPIO_PortSourceGPIOA
#define EXTI_KEY1_GPIO_PinSource GPIO_PinSource0
#define EXTI_Line_KEY1 EXTI_Line0
#define EXTI_KEY1_IRQn EXTI0_IRQn
#define KEY2_INT_GPIO_Pin GPIO_Pin_13
#define KEY2_INT_GPIO_PROT GPIOC
#define KEY2_INT_GPIO_CLK RCC_APB2Periph_GPIOC
#define EXTI_KEY2_CLK RCC_APB2Periph_AFIO
#define EXTI_KEY2_GPIO_PortSourceGPIO GPIO_PortSourceGPIOC
#define EXTI_KEY2_GPIO_PinSource GPIO_PinSource13
#define EXTI_Line_KEY2 EXTI_Line13
#define EXTI_KEY2_IRQn EXTI15_10_IRQn
void EXTI_KEY1_Config(void);
void EXTI_KEY2_Config(void);
#endif /* __BSP_EXTI_H */
// bsp_exti.c 文件
#include "bsp_exti.h"
static void EXTI_KEY1_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStruct.NVIC_IRQChannel = EXTI_KEY1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
static void EXTI_KEY2_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStruct.NVIC_IRQChannel = EXTI_KEY2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
void EXTI_KEY1_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
// 配置 NVIC
EXTI_KEY1_NVIC_Config();
// 初始化GPIO
RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = KEY1_INT_GPIO_Pin;
GPIO_Init(KEY1_INT_GPIO_PROT, &GPIO_InitStruct);
// 初始化EXTI
RCC_APB2PeriphClockCmd(EXTI_KEY1_CLK, ENABLE);
GPIO_EXTILineConfig(EXTI_KEY1_GPIO_PortSourceGPIO, EXTI_KEY1_GPIO_PinSource);
EXTI_InitStruct.EXTI_Line = EXTI_Line_KEY1;
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_KEY2_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
// 配置 NVIC
EXTI_KEY2_NVIC_Config();
// 初始化GPIO
RCC_APB2PeriphClockCmd(KEY2_INT_GPIO_CLK, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = KEY2_INT_GPIO_Pin;
GPIO_Init(KEY2_INT_GPIO_PROT, &GPIO_InitStruct);
// 初始化EXTI
RCC_APB2PeriphClockCmd(EXTI_KEY2_CLK, ENABLE);
GPIO_EXTILineConfig(EXTI_KEY2_GPIO_PortSourceGPIO, EXTI_KEY2_GPIO_PinSource);
EXTI_InitStruct.EXTI_Line = EXTI_Line_KEY2;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
}
// stm32f10x_it.c 文件
#include "stm32f10x_it.h"
#include "bsp_led.h"
#include "bsp_exti.h"
/* 在 stm32f10x_it.c 文件 中新添加两个头文件 : bsp_led.h 与 bsp_exti.h */
/* 在 stm32f10x_it.c 文件 中新添加以下两个中断服务函数 : EXTI0_IRQHandler 与 EXTI15_10_IRQHandler */
void EXTI0_IRQHandler(void)
{
if(EXTI_GetFlagStatus(EXTI_Line_KEY1) != RESET)
{
LED_G_TOGGLE();
}
EXTI_ClearFlag(EXTI_Line_KEY1);
}
// 这里的中断名称不要写成 EXTI4_IRQHandler
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetFlagStatus(EXTI_Line_KEY2) != RESET)
{
LED_B_TOGGLE();
}
EXTI_ClearFlag(EXTI_Line_KEY2);
}
- 在 stm32f10x_it.c 文件 中,如果将 EXTI15_10_IRQHandler 函数写为 EXTI4_IRQHandler 或其他名称,当你按下按键KEY2的时候程序会在 startup.stm32f10x_hd.s 文件中的默认中断服务函数里原地跳转。
// main.c 文件
#include "stm32f10x.h"
#include "bsp_exti.h"
int main(void)
{
LED_G_GPIO_Config();
LED_B_GPIO_Config();
EXTI_KEY1_Config();
EXTI_KEY2_Config();
GPIO_SetBits(LED_PROT, GPIO_Pin_All);
while(1)
{
}
另:
如果PA0、PB0、PC0都连接到 EXTI0 的时候该怎么判断是哪个触发了外部中断?
- 在中断服务函数中判断 GPIOA->IDR、GPIOB->IDR、GPIOC->IDR 的最低位哪个发生了变化,然后进行相应的操作。