EXTI外部中断的执行逻辑|以对射式红外传感器计次为例
对射式红外传感器计次 用到了外部中断 对射式红外传感器接在了GPIOB14上 本文详细介绍了从RCC->GPIO->AFIO->EXTI->NVIC的中断配置过程
分析部分:
RCC(时钟)的配置
RCC配置的必要性:STM32 的所有外设默认处于 “时钟关闭” 状态(低功耗设计),必须先使能时钟,外设才能响应配置和工作。若未使能 GPIOB 时钟,GPIO 配置无效;未使能 AFIO 时钟,EXTI 与 GPIO 的映射会失败,导致外部中断无法触发。
RCC配置的内容:
- GPIOB 时钟(传感器接在 GPIOB14,需 GPIO 外设工作);
- AFIO 时钟(用于 EXTI 与 GPIO 的映射);
- EXTI 本身无需单独使能时钟(EXTI 属于 APB2 外设,随 APB2 时钟默认使能,但需确保 APB2 时钟已开启)。
- NVIC是内核外设,RCC是开启外设的,内核外设不归RCC管理
// 使能GPIOB和AFIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
GPIO(引脚配置)配置
GPIO配置内容:将 GPIOB14 配置为输入模式(外部中断需检测引脚电平变化,必须为输入模式),根据传感器特性选择上拉 / 下拉输入:
- 对射式红外传感器原理:无遮挡时,接收管导通,输出高电平;有遮挡时,接收管截止,输出低电平(或相反,需根据传感器 datasheet 确认)。
- 假设传感器无遮挡时输出高电平,遮挡时输出低电平,因此配置为上拉输入(确保无遮挡时电平稳定为高,避免浮动)。
GPIO_InitTypeDef GPIO_InitStructure; // 配置GPIOB14为上拉输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输入模式下速度无意义,可随便填 GPIO_Init(GPIOB, &GPIO_InitStructure);
AFIO(复用功能配置)配置【AFIO的作用:建立 EXTI 与 GPIO 的 “映射关系”】
AFIO配置内容:EXTI(外部中断控制器)有 16 条中断线(EXTI0~EXTI15),每条线可对应多个 GPIO 引脚(如 EXTI14 可对应 GPIOA14、GPIOB14、GPIOC14 等)。需通过 AFIO 配置EXTI14 与 GPIOB14 的映射,告诉 EXTI:“监测 GPIOB14 的电平变化”。
// 配置EXTI14映射到GPIOB14
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
AFIO配置的必要性:EXTI 本身不直接连接到具体 GPIO 端口(如 A/B/C),而是通过 AFIO 动态映射。若不配置 AFIO,EXTI14 会默认监测 GPIOA14(或其他端口),而非 GPIOB14,导致传感器信号无法触发中断。
EXTI(外部中断控制器)配置【EXTI的作用:定义“中断触发规则”,开启中断的开关】
EXTI配置的内容:配置 EXTI14 的触发方式(上升沿 / 下降沿 / 双边沿)、中断使能,明确 “什么情况下产生中断请求”:
- 对射式传感器计次:通常统计 “遮挡次数”,即当遮挡发生时(电平从高→低)触发中断,因此选择下降沿触发。
EXTI_InitTypeDef EXTI_InitStructure; // 配置EXTI14 EXTI_InitStructure.EXTI_Line = EXTI_Line14; // 对应GPIOB14 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式(区别于事件模式) EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发(遮挡时) EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能EXTI14 EXTI_Init(&EXTI_InitStructure);
EXTI配置的必要性:
- EXTI 是 “中断触发的开关”,需明确触发边沿(否则无法判断何时产生中断)。
- 必须设置为 “中断模式”(EXTI_Mode_Interrupt),才能向 CPU 发送中断请求(事件模式仅触发硬件联动,不产生 CPU 中断)。
NVIC(嵌套向量中断控制器)的配置【NVIC的作用就是 让cpu“响应中断”】
NVIC配置的内容:EXTI 产生中断请求后,需通过 NVIC 配置中断优先级并使能,确保 CPU 能 “感知” 并处理该中断:
- STM32 中,EXTI10~EXTI15 共用一个中断通道(EXTI15_10_IRQn),因此需配置该通道。
/*1. 先设置中断优先级分组(全局生效) 指定抢占优先级和子优先级各占多少位例如:NVIC_PriorityGroup_2 表示:抢占优先级占 2 位,子优先级占 2 位。 此时,抢占优先级可选值为 0~3(2 位共 4 种),子优先级可选值也为 0~3。 抢占优先级:高抢占优先级的中断可以打断低抢占优先级的中断(实现中断嵌套)。 子优先级:当多个中断的抢占优先级相同时,子优先级高的中断先响应(不嵌套,仅决定响应顺序)。*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//2. 再配置具体中断通道的优先级 NVIC_InitTypeDef NVIC_InitStructure; // 配置EXTI15_10中断通道 NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; // EXTI10~15共用此通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级(根据需求设置) NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能该中断通道 NVIC_Init(&NVIC_InitStructure);
在所有 NVIC 中断配置之前必须先配置
NVIC_PriorityGroupConfig,它是
NVIC 配置的全局基础设置。NVIC_PriorityGroupConfig
一次配置,全局统一,不可中途修改分组。其作用是:
- 定义抢占优先级和子优先级的位数分配;
- 决定后续
NVIC_IRQChannelPreemptionPriority
和NVIC_IRQChannelSubPriority
的有效范围;- 必须在具体中断通道配置(
NVIC_Init()
)之前调用,否则会导致优先级配置无效。
NVIC配置的必要性:若不配置 NVIC,即使 EXTI 产生了中断请求,CPU 也会忽略该请求,无法执行中断服务函数。
中断服务函数:实现计次逻辑
中断服务函数的内容:需编写 EXTI15_10 的中断服务函数,在中断中完成计次(需清除 EXTI 中断标志位,避免重复触发):
uint32_t count = 0; // 计次变量void EXTI15_10_IRQHandler(void) {// 检查是否是EXTI14触发的中断if (EXTI_GetITStatus(EXTI_Line14) =SET) {////判断是否是外部中断14号线触发的中断,因为这个函数EXTI15_10都能进来count++; // 遮挡次数+1EXTI_ClearITPendingBit(EXTI_Line14); // 清除中断标志位(必须!)}
}
计次函数
uint16_t CountSensor_Get(void)
{return Count;
}
函数部分
countSensor.c部分
#include "stm32f10x.h" // Device headeruint16_t CountSensor_Count; //全局变量,用于计数/*** 函 数:计数传感器初始化* 参 数:无* 返 回 值:无*///整个外部中断的配置
void CountSensor_Init(void)
{/*开启时钟,不开启时钟,外设是没有办法进行工作的*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟//EXTI和NVIC这两个外设的时钟,是一直打开着的,不需要我们开启时钟。//NVIC是内核内的外设,RCC管理的是内核外的外设,管不着NVIC。/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;//红外接到了PB14GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB14引脚初始化为上拉输入/*AFIO配置外部中断引脚选择*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);// 配置EXTI14映射到GPIOB14/*EXTI初始化,选择触发方式{上升沿、下降沿、双边沿}和触发响应方式{中断响应、事件响应}*/EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量EXTI_InitStructure.EXTI_Line = EXTI_Line14; //选择配置外部中断的14号线EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式(区别于事件模式)EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发,因为GPIOMode是配置的上拉输入模式EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设/*NVIC中断分组,给我们的中断选择一个合适的优先级,最后通过NVIC,外部中断信号进入cpu;NVIC是内核外设,所以它的库函数在misc.h中*///先配置一下NVIC的优先级分组。NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2//即抢占优先级范围:0~3,响应优先级范围:0~3//此分组配置在整个工程中仅能选择一种//若有多个中断,可以把此代码放在main函数内,while循环之前//若封装在各个模块中,需确保每个封装里选择的都是同一个/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //STM32 中,EXTI10~EXTI15 共用一个中断通道(EXTI15_10_IRQn),因此需配置该通道。NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
}/*** 函 数:获取计数传感器的计数值* 参 数:无* 返 回 值:计数值,范围:0~65535*/
uint16_t CountSensor_Get(void)
{return CountSensor_Count;
}/*** 函 数:EXTI15_10外部中断函数* 参 数:无//中断函数都是无参无返回值的* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行* 函数名为预留的指定名称,可以从启动文件复制:从startup——stm32f10x_md.s* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void EXTI15_10_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line14) == SET) //判断是否是外部中断14号线触发的中断,因为这个函数EXTI15_10都能进来{/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0){CountSensor_Count ++; //计数值自增一次}//EXTI_ClearITPendingBit:清除EXTI线路挂起位EXTI_ClearITPendingBit(EXTI_Line14); //中断程序结束后,清除外部中断14号线的挂起位;//中断标志位必须清除//否则中断将连续不断地触发,导致主程序卡死}
}
main.c部分
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化CountSensor_Init(); //计数传感器初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Count:"); //1行1列显示字符串Count:while (1){OLED_ShowNum(1, 7, CountSensor_Get(), 5); //OLED不断刷新显示CountSensor_Get的返回值}
}