一、EXTI是什么?
EXTI = External Interrupt/Event Controller(外部中断/事件控制器)
简单说:EXTI是STM32的"门卫",专门管引脚上的电平变化。当引脚电平变化时,EXTI会通知CPU:"有情况!"
二、EXTI能干什么?
两种工作模式:
-
中断模式:电平变化 → EXTI → CPU → 执行中断函数
-
事件模式:电平变化 → EXTI → 直接触发其他外设(不打扰CPU)
常见用途:
-
按键检测(按一下触发一次)
-
限位开关(碰到就停)
-
外部信号计数(来一个脉冲记一次)
三、EXTI的硬件连接(重要!)
外部引脚 → GPIO端口 → EXTI线路 → NVIC → CPU
↑ ↑ ↑ ↑ ↑
电平变化 端口选择 线路选择 优先级 执行中断
关键规则:
-
每个EXTI线路对应多个GPIO引脚(但有规矩!)
-
同一时间,一个EXTI线路只能连一个GPIO引脚
四、EXTI线路分配表(必须记住!)
| EXTI线路 | 可以连接的GPIO引脚 |
|---|---|
| EXTI0 | PA0, PB0, PC0, PD0, PE0, ... PI0 |
| EXTI1 | PA1, PB1, PC1, PD1, PE1, ... PI1 |
| EXTI2 | PA2, PB2, PC2, PD2, PE2, ... PI2 |
| ... | ...(按数字对应) |
| EXTI15 | PA15, PB15, PC15, PD15, ... PI15 |
重要规律:EXTI0可以连所有端口的引脚0,EXTI1连所有引脚1,以此类推。
五、完整配置步骤(按键为例)
第1步:初始化按键引脚(PA0为例)
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 1. 开启GPIOA时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// 2. 配置PA0为上拉输入(按键按下接地)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; // 输入模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉电阻
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
第2步:配置EXTI中断(核心!)
void EXTI0_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 开启SYSCFG时钟(必须!很多人忘记)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
// 2. 将PA0连接到EXTI0线路
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
// 3. 配置EXTI0线路
EXTI_InitStructure.EXTI_Line = EXTI_Line0; // 线路0
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能
EXTI_Init(&EXTI_InitStructure);
// 4. 配置NVIC中断优先级
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; // 中断源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
第3步:编写中断服务函数
// EXTI0中断服务函数(函数名固定,不能改!)
void EXTI0_IRQHandler(void)
{
// 1. 检查是否是EXTI0触发的中断
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 2. 你的处理代码
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0)
{
// 按键按下的处理
LED_Toggle(); // 切换LED状态
}
// 3. 清除中断标志(必须!不然会一直进中断)
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
六、触发方式详解
| 触发方式 | 说明 | 适用场景 |
|---|---|---|
| 上升沿触发 | 低电平→高电平时触发 | 按键释放检测 |
| 下降沿触发 | 高电平→低电平时触发 | 按键按下检测 |
| 双边沿触发 | 上升和下降都触发 | 电平变化检测 |
// 选择触发方式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; // 双边沿
七、多按键配置示例(多个EXTI线路)
// 配置PA0(EXTI0)和PC13(EXTI13)两个按键
void EXTI_Multi_Init(void)
{
// ... 开启时钟、GPIO初始化 ...
// PA0 -> EXTI0(按键1)
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
// PC13 -> EXTI13(按键2)
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13);
EXTI_InitStructure.EXTI_Line = EXTI_Line13;
EXTI_Init(&EXTI_InitStructure);
// 配置两个中断通道
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; // 注意!10-15共用
NVIC_Init(&NVIC_InitStructure);
}
// 中断服务函数
void EXTI0_IRQHandler(void) // 处理EXTI0
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 处理按键1
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
void EXTI15_10_IRQHandler(void) // 处理EXTI10-15
{
if(EXTI_GetITStatus(EXTI_Line13) != RESET)
{
// 处理按键2(PC13)
EXTI_ClearITPendingBit(EXTI_Line13);
}
}
八、注意事项(避坑指南)
常见错误1:忘记开SYSCFG时钟
// 必须加!
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
常见错误2:中断函数名写错
-
EXTI0-4:
EXTI0_IRQHandler、EXTI1_IRQHandler... -
EXTI5-9:共用
EXTI9_5_IRQHandler -
EXTI10-15:共用
EXTI15_10_IRQHandler
常见错误3:没清除中断标志
会导致重复进入中断,程序卡死!
常见错误4:GPIO模式设错
必须设置为输入模式:GPIO_Mode_IN
九、事件模式 vs 中断模式
// 中断模式:需要CPU参与
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
// 事件模式:直接触发其他外设(如DMA、ADC)
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;
// 事件模式不触发中断,不需要NVIC配置
十、软件触发中断(特殊用途)
// 有时候需要软件模拟一个中断
EXTI_GenerateSWInterrupt(EXTI_Line0); // 软件触发EXTI0中断
十一、消抖处理(实际应用)
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 简单延时消抖
delay_ms(20); // 延时20ms
// 再次检测
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0)
{
// 确实按下了
LED_Toggle();
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
十二、EXTI配置速查表
| 步骤 | 做什么 | 关键函数/配置 |
|---|---|---|
| 1 | 开SYSCFG时钟 | RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG) |
| 2 | GPIO初始化 | 模式=输入,上下拉按需 |
| 3 | 引脚连EXTI | SYSCFG_EXTILineConfig(端口源, 引脚源) |
| 4 | 配置EXTI | 选线路、模式、触发方式 |
| 5 | 配置NVIC | 设优先级 |
| 6 | 写中断函数 | 函数名固定,记得清标志 |
总结口诀
EXTI是门卫,管脚电平变。
先开时钟后连线,SYSCFG别忘记。
引脚线路要对齐,0对0来1对1。
触发方式有三种,上升下降和双边。
中断函数名固定,清除标志要牢记。
按键记得要消抖,实际应用才稳定。