avatar🌌
零醇零醇の小屋

樱花树下,再续前缘

鹤望兰无凋零之时,比翼鸟永世长存

STM32 HAL库入门教程(三):外部中断(EXTI)与 NVIC 详解

上期说了 用按键点灯的教程,但是不知道大家有没有发现一个问题:

在上一期最后,我们写了一句 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 哦! 图1

然后点开左边的 GPIO 设置,点一下 PA0:

  • GPIO mode 选带 Falling edge(下降沿)的那个长长的英文(External Interrupt Mode with Falling edge trigger detection)
  • 下面别忘记还是选 Pull-up 上拉模式哦~ 忘掉的话按钮又要不听话了喵! 图2

还没完哦!装了门铃还得让大管家 NVIC 通电才行! 切换到旁边的 NVIC,把 EXTI line0 interrupt 勾上开启中断开关: 图3

⚠️ 先别急着走!这里有个超容易踩的小坑!( >﹏<。) 看到右边的 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

配置好之后,就生成代码打开工程吧! 图5


4. 揭秘 __weak 与编写中断回调函数 ​

写代码前,我们先来填个坑!还记得上期说的那个神秘的 __weak 吗? 当时让大家假装看不见,现在来揭秘啦(星星眼)!

我们在工程里翻一翻,能找到官方写好的中断回调函数:

c
__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 里面写上我们的代码吧:

c
/* 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 */
图6

锵锵!看一眼上面的 while(1),里面是不是空空如也,一行代码都不用写! 现在不管 while(1) 里在干什么,只要你戳一下按钮,小灯泡就会乖乖听话切换状态,再也不会卡死 CPU 啦!(当然以后学了定时器,还能写得更优雅,这个以后再写喵(

下载、烧录,戳一下按钮试试!

完结撒花!(=^・ω・^=) ​

你已经学会外部中断的魔法啦,快去夸夸自己吧!

最后一次电赛碎碎念:G题周期信号测量分析装置复盘
STM32 HAL库入门教程(二):按键点灯与 GPIO 模式详解
|