软件:STM32-F1系列-EXTI外部中断demo(2026/6/28)
一:总线复用AFIO
相同的pin引脚无法同时触发中断,我们需要先了解 EXTI 的“线路共享”机制。
1. 背后的硬件原理:总线复用
STM32 的 EXTI(外部中断控制器)只有16 条中断线(Line 0 到 Line 15)。
但是,芯片上有A、B、C、D... 等多个 GPIO 端口。
为了让这 16 条线能监控所有端口的引脚,STM32 采用了复用设计:
Pin 0 共用:PA0、PB0、PC0、PD0 ... 全部连接到EXTI Line 0。
Pin 1 共用:PA1、PB1、PC1、PD1 ... 全部连接到EXTI Line 1。
...以此类推,直到 Pin 15。
2. 为什么“不能同时触发”?
既然这些引脚共用同一条 EXTI 线(比如 EXTI Line 0),它们就必须遵守“资源共享”的规则:
互斥性:这条线上只能有一个“活跃”的信号源。如果 PA0 正在触发中断,此时 PB0 也来了个上升沿,系统是无法分辨也不允许两者同时进入中断程序的。
配置唯一:在初始化代码中,你通常只能选择一个具体的引脚(如
GPIO_Pin_0)连接到 EXTI Line 0。如果你试图把 PA0 和 PB0 同时配置给 Line 0,后初始化的那个会覆盖前一个,或者导致配置失败。
3. 实际开发中的影响
这个限制直接决定了代码的写法。如果你想监控多个引脚的状态,通常只有两种方案:
方案一:错开 Pin 号(最常用)
利用“不同 Pin 可以共用 Line”的特性。例如,你可以用
PA0(Line 0)、PA1(Line 1)、PA2(Line 2) 来触发三个不同的中断,互不影响。方案二:同一 Pin 号轮流查(轮询法)
如果你非要用
PA0和PB0两个引脚,且都需要即时响应,因为 EXTI 做不到,你就只能在中断发生后,用if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0))去读取寄存器判断到底是谁触发的。
总结
这句话的意思是:在同一个时刻,同名的引脚(如 0 号脚)只能有一个连接到 EXTI 中断线上。这是由硬件线路复用决定的,因此在写代码时,你不能把 PA0 和 PB0 同时设为外部中断输入,这个过程是通过AFIO实现的。
二:图中PVD,RTC,USB,ETH是怎么通过gpio触发中断的,举例工业实例一般怎么用的
在介绍具体实例之前,必须先纠正一个看图时最容易产生的关键误区:这些模块(PVD、RTC、USB、ETH)并不是通过 GPIO 引脚来触发中断的。
1. 核心误区纠正:内部信号 vs 外部引脚
左侧的 GPIO:代表外部物理引脚。信号需要从芯片外面穿过引脚,进入芯片内部。这中间必须路过红色的 AFIO(中断引脚选择) 模块,由 AFIO 决定到底是
PA0还是PB0连接到 EXTI 线上。下方的 PVD/RTC/USB/ETH:代表芯片内部的硬件外设。它们产生中断信号完全是内部事件,不需要经过外部的引脚和 AFIO 模块,而是直接通过内部连线“递交给”橙色的 EXTI(边沿检测及控制) 模块。
它们之所以出现在这张图里,是因为 STM32 的 EXTI 控制器总共有 20 个输入源,除了 16 个给 GPIO 用,剩下的 4 个(Line 16~19)正好被分配给了这四个内部外设。
2. 这些内部中断在工业场景中怎么用?
虽然它们不走 GPIO,但它们在工业设计中的“戏份”往往比普通按键还要重。以下是典型的工业应用实例:
①:PVD (可编程电压检测器) —— 工业设备的“保命符”
它是干嘛的:它像一个高精度的“电源哨兵”,实时盯着芯片的供电电压(VDD)。一旦电压掉到危险线以下,它会立刻拉响警报。
工业痛点:工业现场经常有大型电机启动,这会导致瞬间电压骤降(俗称“晃电”)。
应用场景:假设一台工业控制器正在控制一台重型机床。突然电网波动导致电压急剧下降。如果在电压彻底崩溃导致单片机复位前,PVD 提前发出了中断信号,CPU 就能利用这宝贵的几百毫秒时间,紧急刹车把机床停稳,或者把当前的加工进度、故障代码死死保存到 Flash 里。如果没有 PVD,设备可能直接黑屏重启,导致半成品报废甚至引发安全事故。
②:RTC (实时时钟) —— 无人值守的“闹钟”
它是干嘛的:提供精准的时间和日历功能。
工业痛点:很多工业设备(如远程水泵站、环境监控箱)是放在野外或无人车间常年运行的,不可能派个人天天去按开关。
应用场景:定时任务。比如利用 RTC 的闹钟中断(EXTI Line 17),设置在每天凌晨 2:00 系统负载最低时,让单片机自动醒来,采集一次传感器数据并通过 4G 模块上传到云端,然后再乖乖睡去,从而实现极致的低功耗
③:USB & ETH (USB 与以太网) —— 通讯的“唤醒键”
它是干嘛的:这两个模块通常作为“唤醒源”存在。
工业痛点:为了省电,工业网关在空闲时通常会进入深度休眠(Stop/Standby 模式),此时 CPU 是不工作的。但厂家又希望能随时远程唤醒它去升级程序或查看状态。
应用场景(WOL 网络唤醒):这就是工业物联网(IIoT)的标配。比如一个工厂里有一排 STM32 控制的智能电表处于休眠状态。此时,工程师在远端服务器上向这排电表发送一个特定的“魔术数据包”(Magic Packet)。网口(ETH)收到后,硬件电路自动触发中断(EXTI Line 19),瞬间将整个主板从深度睡眠中“喊醒”,CPU 随即开始执行数据采集任务。
三:EXTI中的中断响应和事件响应的区别。
这是一个非常关键的问题,也是 STM32 中断系统里最容易混淆的概念。
结合你老师给的那张框图,我们可以把“中断响应” 和事件响应 的区别,用一句话先总结:
中断响应是“找 CPU 干活”,事件响应是“让硬件自己干活”。
✅中断响应:EXTI →NVIC →CPU
✅事件响应:EXTI →Pulse →硬件外设
1)中断响应(Interrupt Response)
2)事件响应(Event Response)
实现工业自动化,事件响应,入门系列用的不多。
3)工业实例
四:外部中断EXTI的一个小demo
#include "stm32f10x.h" // Device header uint16_t Count; void Exti_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_13; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource13); //实现了GPIO->AFIO->EXTI的链接 EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line=EXTI_Line13; EXTI_InitStructure.EXTI_LineCmd=ENABLE; EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; EXTI_Init(&EXTI_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//选择中断向量组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); } void EXTI15_10_IRQHandler(void)//每一个中断函数都有自己的名字,不能改的,在start中找 { if(EXTI_GetITStatus(EXTI_Line13)==SET)//判断我们的中断是从线13进来的,而不是其余的中断口 { Count++; EXTI_ClearITPendingBit(EXTI_Line13);//清除中断标志位 } } uint16_t Count_Return (void) { return Count; }五:中断函数名字查找
在启动文件中,给每一个中断函数均分配了一个名字。
六:两个中断易混淆函数
EXTI_GetFlagStatus(uint32_t EXTI_Line);
EXTI_GetITStatus(uint32_t EXTI_Line);
EXTI_ClearFlag(uint32_t EXTI_Line);
EXTI_ClearITPendingBit(uint32_t EXTI_Line);
1)EXTI_GetFlagStatus(uint32_t EXTI_Line);
使用这类“查询/轮询(Polling)”函数(EXTI_GetFlagStatus),如果不做特殊处理,CPU 确实会被一直占用,无法执行其他任务,这正是“中断(Interrupt)”技术诞生的原因。我们来详细看看这两种模式对 CPU 的影响:
1. 轮询模式:CPU 的“死盯战术”
如果你在主循环里写这样的代码:
while(1) { if (EXTI_GetFlagStatus(EXTI_Line1) != RESET) { EXTI_ClearFlag(EXTI_Line1); } }工作状态:CPU 就像个尽职的保安,眼睛死死盯着红灯。哪怕红灯没亮,它也在不停地看(消耗算力)。
后果:CPU不能休眠,无法做其他复杂计算。如果系统里有别的任务(比如刷屏幕、算数据),这种死盯会严重影响整体性能。
2. 中断模式:CPU 的“守株待兔”
工作机制:CPU 平时根本不管门铃。只有当硬件强制打断它时,它才停下来去处理。
优势:CPU利用率极高。没事的时候它能休息,有急事(中断)来了才工作。
3.如何优化“轮询”以减少 CPU 占用?
可以用一个小技巧——加延时,让它“喘口气”:
Delay_ms(10); // 让 CPU 睡 10 毫秒,这段时间不查询
效果:虽然还是会占用 CPU,但占用量从100% 降到了大概10%(剩下的 90% 在 Delay 里休眠)。
代价:按键会有 10ms 的延迟。
所以,现代高级的单片机编程,极其推崇使用中断(EXTI_GetITStatus),尽量让 CPU 在没有事情的时候“睡觉”,这样既能省电,又能留出算力给其他复杂任务。
