STM32 中断体系深度解析:基于 HAL 库的按键控制 LED 完整流程(从原理到代码)

发布于:2025-07-30 ⋅ 阅读:(20) ⋅ 点赞:(0)

一、按键消抖与中断:从问题到 HAL 库实现(新手必懂的按键处理逻辑)

(一)、问题引入:为什么会 “丢失按键”?

(1)死循环检测的缺陷

用 while(1) 死循环读取按键引脚电平,就像 “盲人摸象”—— 如果按键动作(电平变化)发生在两次检测之间,程序就会 “看不见”。

  • 按键按下→松开→按下→松开,产生 4 次电平变化(①→②→③→④)
  • 但程序如果在④位置才检测,就会丢失中间 2 次按键动作

(二)生活类比理解

想象你在看书(主程序),每隔 10 分钟才抬头看一眼门铃(死循环检测):

  • 如果门铃在 “非抬头时间” 响(按键动作),你就会错过(丢失按键)!
  • 解决办法:门铃响时主动打断你(中断机制),不管你在看哪页书,都会立即处理。

二、中断机制:解决 “丢失按键” 的钥匙

(一)中断处理流程

中断就像 “紧急门铃”,能主动打断主程序,流程分 7 步:

  1. 主程序正常执行(①)
  2. 按键按下→触发中断(②)
  3. 暂停主程序(③)→保存当前状态(比如 “看到书的第几页”)(④)
  4. 跳转到中断任务(⑤)→执行按键处理逻辑(⑥)
  5. 恢复主程序状态(⑧)→继续执行(⑨)

(二)中断的硬件架构

CPU 运行时,会被各种 “异常” 打断,按键是其中一种中断源

  • 按键、定时器、ADC 等事件→通过 “中断控制器” 汇总→通知 CPU
  • 中断控制器像 “前台”,帮 CPU 筛选最紧急的事件(比如同时按键和定时器中断,优先处理高优先级的)

三、ARM 芯片中断处理全流程

(一)准备阶段:给中断 “搭好台子”

想象 ARM 芯片是个 “多功能工厂”,中断就是工厂里的 “紧急任务通道”。要让这个通道能用,得先做 3 件事:

  1. 指定谁能触发中断(设置中断源):
    比如告诉芯片 “当按键被按下时,要走紧急通道”,让按键这个 “触发事件” 能唤醒中断机制。
  2. 设置任务优先级(配置中断控制器):
    工厂里可能同时有多个紧急任务(按键、定时器、串口数据),得规定谁先处理。比如 “按键中断比普通定时器中断更紧急”,避免重要任务被耽误。
  3. 打开紧急通道总开关(使能 CPU 中断):
    就算设置了触发条件和优先级,要是总开关没开,紧急任务来了也没人理,所以得给 CPU 说 “允许处理中断”。

完成这三步,芯片就 “知道要监听哪些紧急事件、怎么排序、要不要响应” 了,接下来就能正常跑主程序(比如工厂里的常规生产流程)。

(二)触发阶段:中断是怎么 “跳出来” 的

当你按下按键(触发中断源),就像给工厂打了个 “紧急电话”:

  1. 按键把信号传给中断控制器(工厂的 “调度室”),调度室确认 “这是个要处理的中断”。
  2. CPU 每干完一件小事(执行完一条指令),都会 “瞄一眼” 调度室(检查有没有中断)。
  3. 一旦发现有紧急任务(中断触发),CPU 就会暂停手头的活(比如主程序里的循环),准备处理中断。

(三)处理阶段:中断函数是怎么执行的

CPU 暂停主程序后,会做三件事(像 “紧急任务处理流程”):

  1. 记下来当前干到哪了(保存现场):
    把主程序执行到一半的状态(比如变量值、执行到哪行代码)存起来,就像看书看到一半插个书签。
  2. 找到对应的处理方法(跳转到中断函数):
    芯片里有个 “紧急任务手册”(异常向量表),写着 “按键中断该调用哪个函数处理”。CPU 会根据中断类型,找到对应的函数(比如 “按键按下后翻转 LED” 的逻辑)。
  3. 处理完回到书签继续干(恢复现场):
    中断函数执行完(比如 LED 翻转完成),CPU 会把之前存的主程序状态读回来,继续从暂停的地方接着执行(就像从书签位置继续看书)。

(四)总结:中断处理像 “临时插播”

整个流程就像:
你正在看电影(主程序)→ 门铃突然响了(中断触发)→ 你暂停电影、记住看到哪(保存现场)→ 去开门(执行中断函数)→ 回来接着从暂停处看电影(恢复现场)。

ARM 芯片的中断处理,本质就是这套 “暂停 - 处理 - 恢复” 的逻辑,不管多复杂的中断,都是这个思路~

四、中断触发全流程拆解:从按键按下到执行函数

(一)源头:按键如何触发中断信号?

把按键想象成 “门铃”,要让门铃能触发中断,得完成 3 步 “接线 + 配置”:

1. 第一步:选 “门铃线”(配置中断源映射)

  • STM32 有很多 GPIO 引脚(PA0、PB0…PG0),但 EXTI 中断线只有 16 条(EXTI0~EXTI15)
  • 想让 PA0 按键触发中断,得把 PA0 “连” 到 EXTI0 线上 —— 这就需要配置 AFIO_EXTICR 寄存。

看寄存器图(AFIO_EXTICR1):

  • EXTI0[3:0] 这 4 位,决定 EXTI0 线连到哪个 GPIO。
  • 如果设置为 0000,就表示 EXTI0 连到 PA0;设 0001 则连到 PB0,以此类推。

白话解释:就像给门铃选 “入户线”,你得告诉系统 “PA0 这个按键,走 EXTI0 这条中断线”。

2. 第二步:设置 “门铃响的方式”(配置触发模式)

  • 按键按下时,电平会变化:可能是 “从高变低”(下降沿),也可能是 “从低变高”(上升沿)。
  • 你得告诉 EXTI 控制器:“当 PA0 电平出现下降沿时,触发中断”(或上升沿,看需求)。

对应手绘原理图里的 “触发方式”:在代码里配置 GPIO_MODE_IT_FALLING(下降沿触发)或 GPIO_MODE_IT_RISING(上升沿触发)。

3. 第三步:打开 “门铃接收器”(使能 EXTI 中断)

  • 即使接了线、设了触发方式,EXTI 控制器也得 “允许接收” 这个中断。
  • 这一步需要 使能 EXTI 对应的中断线(比如 EXTI0),让它能把信号传给 NVIC(中断控制器)。

(二)中转站:NVIC 如何 “调度” 中断?

NVIC 是 “中断调度中心”,它要做 2 件事:

1. 第一步:设置 “优先级”(谁先处理)

  • 系统可能同时触发多个中断(比如按键中断 + 定时器中断),得规定谁更紧急。
  • 通过 NVIC 配置 “抢占优先级” 和 “子优先级”:数值越小,优先级越高。
  •  优先级裁决:确定中断响应顺序(谁先处理)

    系统可能同时触发多个中断(如按键中断 + 定时器中断 ),NVIC 靠 “抢占优先级 + 子优先级” 区分紧急程度,规则如下:

  • 抢占优先级:决定 “中断能否嵌套” 。数值越小,优先级越高,高抢占优先级中断可打断低抢占优先级的中断执行(类似 “紧急任务插队” )。比如 EXTI0 抢占优先级为 0 ,EXTI1 为 1 ,若 EXTI0 正在执行,EXTI1 触发不会打断它;但如果 EXTI1 抢占优先级是 0 、EXTI0 是 1 ,EXTI1 就能抢占执行。
  • 子优先级:当多个中断 “抢占优先级相同” 时,子优先级高(数值小)的先被响应;若抢占、子优先级都相同,编号小的中断优先。比如 EXTI0 和 EXTI1 抢占优先级相同,子优先级高的 EXTI0 会先处理;若子优先级也相同,EXTI0(编号小)优先。
【新增关键补充】优先级寄存器的 8 位 “隐藏规则”

很多初学者会疑惑:NVIC 里每个中断的优先级用 8 位寄存器存,这 8 位都咋用?这里藏着两个关键细节

  • ① 8 位不一定全实现:像 STM32F103 系列芯片,实际只实现了 4 位 。芯片厂商会根据硬件复杂度、功耗等需求,决定真正启用几位优先级位,具体看芯片手册。
  • ② 分组优先级和子优先级的划分可配置:NVIC 内部有 “优先级分组配置寄存器” ,它决定 8 位里 “哪些位算分组优先级(抢占用)、哪些位子优先级(同抢占时排序用)” 。比如:
    优先级分组 抢占优先级域 子优先级域
    0(默认) Bit[7:1] Bit[0]
    1 Bit[7:2] Bit[1:0]
    ... ... ...
    不同分组下,抢占和子优先级的位数拆分不同,配置时用 HAL_NVIC_SetPriorityGrouping() 函数设置(比如 HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_3); ),选好后所有中断的优先级拆分规则就跟着变啦。

代码配置示例(以 EXTI0 为例 ):

// EXTI0_IRQn:中断通道;第一个 0:抢占优先级;第二个 0:子优先级  
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);  

对应硬件原理:每个中断在 NVIC 里对应 8 位优先级寄存器(如 NVIC->IP[0] 对应外部中断 #0 ),寄存器被拆分为 “抢占优先级 + 子优先级” 两部分,通过代码配置这 8 位寄存器的值,实现优先级设定。

2. 总开关使能:允许中断到达 CPU

即使外设(如 EXTI )触发了中断信号,NVIC 还需 “允许转发” 给 CPU ,才能真正响应中断。

代码实现(以 EXTI0 为例 ): 

// 使能 EXTI0 对应的 NVIC 中断通道,让中断信号能传递到 CPU  
HAL_NVIC_EnableIRQ(EXTI0_IRQn);  

这一步是 “中断生效的最后开关” ,配置了优先级但未使能通道,中断依然无法被 CPU 响应。

简单说,NVIC 调度中断就像 “先给中断排好紧急程度(优先级裁决,含 8 位寄存器的分组秘密),再打开通道让紧急信号能传到 CPU 处理(使能开关)” ,确保系统按规则响应最紧急的中断 。

(三)终点:CPU 如何执行中断函数?(结合向量表)

当 NVIC 把中断信号传给 CPU 后:

1. 第一步:查 “导航表”(异常向量表)

  • CPU 每执行完一条指令,都会 “检查有没有中断”。一旦发现有中断,就去查 异常向量表
  • 向量表里存着 “每种中断对应的处理函数地址”,比如 EXTI0 中断对应 EXTI0_IRQHandler 函数。

2. 第二步:执行 “中断逻辑”(回调函数)

  • EXTI0_IRQHandler 是硬件规定的入口函数,它会调用 HAL 库的 HAL_GPIO_EXTI_IRQHandler
  • 最终会执行我们写的 回调函数 HAL_GPIO_EXTI_Callback,在这里写按键逻辑(比如翻转 LED)。

(四)总结:中断触发的完整流程

把整个过程想象成 “寄快递”:

  1. 按键按下 = 你把快递(中断信号)放到门口。
  2. EXTI 配置 = 选快递通道(EXTI0 线)、写快递要求(触发方式)、激活通道(使能 EXTI)。
  3. NVIC 调度 = 快递站(NVIC)看快递优先级(谁先送)、决定是否转发给 CPU(使能通道)。
  4. CPU 执行 = 快递员(CPU)查地址表(向量表)、找到收件地址(处理函数)、送快递(执行逻辑)。

五、STM32 GPIO 中断按键控制超详细教学(零基础友好版)

(一) 为什么用中断控制按键?

之前学按键,是不是一直用while(1)死循环检测?这样 CPU 啥都干不了,只能盯着按键,太浪费啦!中断方式 就像给按键装了 “报警器”,按下时主动喊 CPU 来处理,平时 CPU 该干嘛干嘛,效率直接拉满~今天带大家用 STM32 实现「按键中断控制 LED」,超详细步骤,跟着做一定能成!

(二)硬件原理 —— 按键怎么和 STM32 互动?

先看硬件电路:

  • 按键没按下:PB14 通过电阻(或内部上拉)保持高电平
  • 按键按下:PB14 直接连 GND,变成低电平
    STM32 就是通过检测 PB14 的电平变化(高→低 / 低→高)触发中断,实现按键功能!

(三)软件流程 —— 从配置到代码的完整步骤

回忆老师讲的中断流程,咱们一步步来:

步骤 1:用 CubeMX 配置 GPIO(图形化操作超简单)

打开 STM32CubeMX,找到你的开发板(比如 STM32F103C8T6 ),做这 2 件事:

(1)配置按键引脚(PB14)
  • 找到 PB14 引脚,右键选择 External Interrupt Mode with Rising/Falling Edge(上升沿 + 下降沿触发,按键按下、松开都能触发中断)
  • 上下拉电阻:如果硬件没外接电阻,选 No pull-up and no pull-down 也能用(实际建议选内部上拉更稳定,看需求调整)​​​​​​​
(2)配置 LED 引脚(比如 PC13)
  • 找到 PC13(开发板常用小灯引脚),配置为 Output Push Pull(推挽输出模式,控制灯亮灭)

步骤 2:配置 NVIC—— 让中断被 “听到”

NVIC 是 STM32 的 “中断管家”,得告诉它:“这个按键中断要优先处理!”

  • 点击左侧 NVIC,找到 EXTI line[15:10] interrupts(因为 PB14 对应 EXTI14,属于这一组)
  • 勾选 Enabled 启用中断
  • 设置优先级:Preemption Priority(抢占优先级) 和 Sub Priority(子优先级) 填 0 就行(数值越小优先级越高,简单场景不用纠结)

步骤 3:生成代码 ——CubeMX 帮你写框架

点击 GENERATE CODE,CubeMX 会生成初始化代码,重点看这 2 个文件:

  • stm32f1xx_hal_msp.c:里面有 GPIO 和 NVIC 的初始化代码(不用管,CubeMX 自动配好的)
  • main.c:主函数逻辑,我们要在这里写中断回调

步骤 4:写中断回调函数 —— 按键逻辑在这里

STM32 HAL 库有个 “回调函数” 机制,中断触发后会自动调用 HAL_GPIO_EXTI_IRQHandler,它会帮我们清中断标志,然后调用 HAL_GPIO_EXTI_Callback—— 我们只需要重写这个回调函数!

在 main.c 里添加代码(或者自己建个文件,记得包含头文件):

// 中断回调函数,按键触发时自动执行
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    // 判断是不是 PB14 触发的中断
    if (GPIO_Pin == GPIO_PIN_14) 
    {
        // 读取 PB14 当前电平:按下是低电平,松开是高电平
        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET) 
        {
            // 按键按下:控制 PC13 灯亮(假设小灯接 PC13)
            HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);  
        } 
        else 
        {
            // 按键松开:控制 PC13 灯灭
            HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);  
        }
    }
}

(四)下载验证 —— 按下按键,看灯反应!

把代码编译、下载到开发板,试试:

  • 按下按键 K1(接 PB14 )→ PC13 灯亮
  • 松开按键 → PC13 灯灭

如果没反应,检查这 3 点:

  1. 硬件连接:按键真的接 PB14、灯接 PC13 吗?
  2. CubeMX 配置:中断模式、NVIC 使能有没有漏?
  3. 代码逻辑HAL_GPIO_EXTI_Callback 里的引脚判断对不对?

(五)完整流程(核心逻辑总结):
(按键按下 → PB14 电平变化 → EXTI 触发中断 → NVIC 转发 → CPU 执行回调函数 → 控制 PC13 灯亮灭)

(六)一句话总结 —— 学会中断按键有多爽?

用中断控制按键,CPU 不用 “死等” 按键,效率翻倍!核心就 4 步:
CubeMX 配 GPIO → 配 NVIC 优先级 → 写回调函数 → 下载验证

下次做项目,不管是按键控制灯、还是触发复杂逻辑,都能用这套方法~快试试吧


网站公告

今日签到

点亮在社区的每一天
去签到