1. EXTI
1.1 EXTI的简介
• EXTI 是 External Interrupt 的缩写,表示外部中断事件控制器 。EXTI 可以监测指定 GPIO 口 的电平信号变化,并在检测到指定条件时,向内核的中断控制器NVIC 发出中断申请。NVIC 在裁决后,如果满足条件,会中断CPU的主程序,使CPU转而执行EXTI对应的中断服务程序。
• EXTI 支持的触发方式:上升沿、下降沿、双边沿或软件触发。并且上升沿、下降沿、双边沿这三种方式是常用触发方式,都可以在配置GPIO口的mode中配置,上升沿(GPIO_MODE_IT_RISING),下降沿(GPIO_MODE_IT_FALLING),双边沿(GPIO_MODE_IT_RISING_FALLING)。
• EXTI 支持所有的GPIO口,但需要注意的是,相同的 Pin 不能同时触发中断。例如,PA0 和 PB0 不能同时被 配置为中断源。
• EXTI 提供了16 个 GPIO_Pin 的中断线 ,以及额外的中断线如 PVD 输出、RTC 闹钟 、USB 唤醒和以太网唤醒。(注意只有互联网芯片才有以太网)。
1.2 中断和事件区别:
• 中断会打断CPU当前正在执行的程序,转而去执行中断服务程序,待中断服务程序执行完毕后,CPU会返回到原来的程序执行点继续执行。
• 事件只是简单地表示某个动作或状态的变化 ,而不会打断CPU当前正在执行的程序。当事件发生时,它会根据配置 来决定是否触发相应的中断。如果开放了对应的**中断屏蔽位,**事件就可以触发相应的中断,如果没有开放相应的中断屏蔽位,事件只会作为一个信号存在,不会被CPU处理。
1.3 EXTI基本结构,如图:

2. AFIO
• AFIO 是 Alternate Function Input/Output 的缩写,表示复用功能IO ,主要用于实现 IO 端口的复用功能 以及外部中断的控制。
• STM32上有很多 IO口以及内置外设(如I2C、ADC、SPI、USART等)。为了节省引出引脚的数量,这些内置外设通常与 IO 口共用管脚,即 IO 管脚具有复用功能。例如,一个 GPIO 管脚除了可以作为普通的 IO端口外,还可以被复用为某个内置外设的功能引脚。
• 然而,为了优化64脚或100脚封装的外设数量,有时需要将一些复用功能重新映射到其他引脚上。这时,就可以使用AFIO的复用重映射功能。通过设置复用重映射和调试IO配置寄存器(AFIO_MAPR),可以实现引脚的重新映射,使得复用功能不再映射到它们的原始分配上。
• 此外,AFIO 还用于控制外部中断 ,用来配置 EXTI 中断线 0~15 对应哪个具体 IO 口。
• 当需要使能外部中断线 或进行外部中断线的映射 时,通常需要开启AFIO 的时钟。
• AFIO与IO对应关系,如图:

3. EXIT的配置流程
• 使能GPIO时钟__HAL_RCC_GPIOx_CLK_Enable();
• 设置GPIO输入模式
• 设置AFIO(开启时钟,IO映射)
• 设置EXTI(屏蔽,上下沿)
• 以上三步均在HAL_GPIO_init()函数进行配置,HAL库函数已经对相应寄存器进行封装了
• 设置NVIC(优先级分组,设置优先级,使能中断)
• 设置中断服务函数(EXTIx_IRQHandler())
4. 实操,按键电灯(中断法),按下 KEY1 翻转 LED1 状态,而 LED2 一直保持 500ms 的频率闪烁。
4.1 main.c
cpp
#include "sys.h"
#include "exti.h"
#include "delay.h"
#include "led.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* LED初始化 */
exti_init();
while(1)//流水灯实验
{
led2_on();
delay_ms(500);
led2_off();
delay_ms(500);
}
}
4.2 led.c
cpp
#include "led.h"
void led_init(){
GPIO_InitTypeDef gpio_init = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();//使能GPIO时钟
gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init.Pin = GPIO_PIN_8 | GPIO_PIN_9;//推挽输出
gpio_init.Pull = GPIO_PULLUP;//默认上拉
gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB,&gpio_init);//初始化gpio
led1_off();//关灯
led2_off();
}
void led1_on(){
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
}
void led1_off(){
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
}
void led1_toggle(){
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
}
void led2_on(){
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET);
}
void led2_off(){
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET);
}
void led2_toggle(){
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
}
4.3 exti.c
cpp
#include "exti.h"
#include "delay.h"
#include "led.h"
void exti_init(){
GPIO_InitTypeDef gpio_init = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIO时钟
gpio_init.Mode = GPIO_MODE_IT_FALLING;//下降沿触发中断
gpio_init.Pin = GPIO_PIN_0;//推挽输出
gpio_init.Pull = GPIO_PULLUP;//默认上拉
gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
HAL_NVIC_SetPriority(EXTI0_IRQn,2,0);//设置优先级
HAL_NVIC_EnableIRQ(EXTI0_IRQn);//使能中断
}
void EXTI0_IRQHandler(){//中断服务函数没参数和返回值
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);//中断服务公共函数
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
delay_ms(20);//消抖
if(GPIO_Pin == GPIO_PIN_0){
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET)
led1_toggle();
}
}