上期说了 用按键点灯的教程,但是不知道大家有没有发现一个问题:
在上一期最后,我们写了一句 while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 0);,意思是只要手指按着按钮不松开,CPU 就会一直卡在这里死等,什么别的事都干不了( 而且如果主循环里还有第一期的 HAL_Delay(500) 闪灯,那 CPU 睡大觉的时候,你按按钮它是完全不会理你的!哼(`へ´)
肯定有人问:那怎么让 CPU 平时安心干自己的活,只有按按钮的时候才理我呢??? 这时候就要请出今天的可爱小帮手——**外部中断(EXTI)**啦!(。・ω・。)ノ♡
1. 什么是中断?认识 EXTI 与 NVIC
听名字好像很厉害对吧?别怕别怕,其实就是装了个门铃喵~
- 上期我们在
while(1)里一遍遍问“按了吗?按了吗?”,这叫**【轮询】**。就像你点了草莓蛋糕外卖,但是家里没门铃,只能每隔 5 秒跑到门口偷看一眼,累都累死了呜呜( - 而**【中断】**呢,就是给家里装了个叮咚门铃!平时 CPU 只管在
while(1)里愉快地摸鱼干活;当你按下按钮,门铃一响(触发中断),CPU 就暂停手里的活,跑去把灯翻转一下,然后乖乖回来继续干活!
是不是超级聪明,喵~ (✧ω✧)
在正式接线装门铃之前,我们先认识两位幕后小功臣:EXTI 和 NVIC!
① EXTI(外部中断控制器)—— 守在大门口的门卫小妹
STM32 有好多好多引脚(PA0~PA15、PB0~PB15……),EXTI 专门准备了 16 条给引脚用的门铃线(EXTI0 ~ EXTI15)。
💡 注意哦:尾号相同的引脚是共用同一条门铃线的! 比如
PA0、PB0、PC0全都归EXTI0管(但同一时间只能选其中一个接上去,不能让PA0和PB0同时当EXTI0,不然门卫小妹就分不清是谁在按门铃啦喵~)。
② NVIC(嵌套向量中断控制器)—— 管秩序的大管家
单片机里除了按键门铃,以后还会有很多别的门铃(比如定时器中断、串口中断)。 要是两个门铃一起响,或者 CPU 正在拿草莓蛋糕的时候,奶茶外卖又来敲门了,先理谁呢?
这就全靠大管家 NVIC 来排队管秩序啦!它手里有两个优先级指标(记住口诀:数字越小,地位越高哦!0 是大姐头,15 是小跟班):
- 抢占优先级(Preemption Priority):决定能不能插队打断别人!如果 A 的抢占优先级比 B 高,那就算 B 正在执行,A 也能直接一脚把门踹开插队进去先执行(这就叫**“中断嵌套”**!)。
- 响应优先级 / 子优先级(Sub Priority):当两个中断的抢占优先级一模一样(平起平坐),而且刚好同一瞬间按响门铃时,大管家就看谁的响应优先级高先理谁;但如果其中一个已经在屋里干活了,另一个是不能插队打断它的哦!
2. 边沿触发:门铃是怎么响的?
那门铃是怎么响的呢?靠的是电平变化的**“边沿”**哦! 看图: 
- 上升沿:电平从
0变成1的瞬间(咻——往上跳!) - 下降沿:电平从
1变成0的瞬间(啪——往下掉!)
上期我们的按钮接了上拉电阻,没按的时候是 1,按下去变成 0。 所以按下去的那一下,是从 1 掉到 0,我们选**【下降沿触发】**就好啦!
3. CubeMX 工程配置
打开我们熟悉的 CubeMX,选择我们的 STM32F103C8T6,老规矩配好时钟和调试口(忘掉的同学罚抄第一期十遍)。
PC13 还是配成 GPIO_Output 当小灯泡。 **重点来啦!**点一下 PA0,这回不选 Input 了,选 GPIO_EXTI0 哦! 
然后点开左边的 GPIO 设置,点一下 PA0:
GPIO mode选带Falling edge(下降沿)的那个长长的英文(External Interrupt Mode with Falling edge trigger detection)- 下面别忘记还是选
Pull-up上拉模式哦~ 忘掉的话按钮又要不听话了喵!
还没完哦!装了门铃还得让大管家 NVIC 通电才行! 切换到旁边的 NVIC,把 EXTI line0 interrupt 勾上开启中断开关: 
⚠️ 先别急着走!这里有个超容易踩的小坑!( >﹏<。) 看到右边的 Preemption Priority(抢占优先级)了吗?刚刚我们说了,数字越小,地位越高(0 是大姐头,15 是小跟班)。 因为我们待会消抖要用 HAL_Delay,而它是靠上面的 Time base: System tick timer 来数数的。 结果默认设置里,EXTI0 是 0(大姐头),tick timer 却是 15(小跟班)! 大姐头干活的时候,小跟班根本不敢插嘴数数,HAL_Delay 就会永远卡住死机啦! 所以我们把 EXTI line0 的数字改成 1,再把 Time base: System tick timer 改成 0,让它乖乖听话就好啦~ (当然,这个做法不是标准做法,此处仅为了学习 EXTI,开发中不可以随便调 tick timer 的优先级,会把程序炸掉的)
配置好之后,就生成代码打开工程吧! 
4. 揭秘 __weak 与编写中断回调函数
写代码前,我们先来填个坑!还记得上期说的那个神秘的 __weak 吗? 当时让大家假装看不见,现在来揭秘啦(星星眼)!
我们在工程里翻一翻,能找到官方写好的中断回调函数:
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
UNUSED(GPIO_Pin);
}这个 __weak 叫**“弱定义”**,就像官方给你留好的空盘子! 你不写的时候,它就空着什么都不干;一旦你在自己的 main.c 里写了一个一模一样名字的函数(自己写的时候去掉 __weak 哦),编译器就会自动把你写的美味代码装进盘子里端上去! 里面的参数 GPIO_Pin 就是告诉你“是谁按的门铃”,比如 PA0 按了,传进来的就是 GPIO_PIN_0。 理解了吗,喵~ (=´ω`=)
好啦,现在翻到 main.c 最下面,在 USER CODE BEGIN 4 里面写上我们的代码吧:
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_0)
{
HAL_Delay(20);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 0)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
}
}
/* USER CODE END 4 */
锵锵!看一眼上面的 while(1),里面是不是空空如也,一行代码都不用写! 现在不管 while(1) 里在干什么,只要你戳一下按钮,小灯泡就会乖乖听话切换状态,再也不会卡死 CPU 啦!(当然以后学了定时器,还能写得更优雅,这个以后再写喵(
下载、烧录,戳一下按钮试试!
完结撒花!(=^・ω・^=)
你已经学会外部中断的魔法啦,快去夸夸自己吧!
