学习 GPIO 输入 是迈向“人机交互”的关键一步。现在我们将以 PA0 引脚连接按键,PC13 引脚控制 LED 为例。学习两种最常用的方法:轮询扫描(Polling) 和 外部中断(EXTI)
准备工作:硬件连接
- LED: 最小开发板自带,连接在 PC13(低电平点亮)。
- 按键: 找一个轻触按键。
- 一端连接到 PA0。
- 另一端连接到 GND。
- 原理:平时 PA0 是高电平,按键按下时 PA0 变成低电平。
实验一:轮询扫描(Button Scanning)
这种方法就像警察“查岗”,在 while(1) 循环里不断地看 PA0 的状态。
1. CubeIDE 配置 (ioc 文件)
- PC13: 保持
GPIO_Output【最小开发板上的灯,低电平点亮】 - PA0: 设置为
GPIO_Input。 - 关键配置 (GPIO 选项卡):
- 点击 PA0,将 GPIO Pull-up/Pull-down 设置为 Pull-up(上拉)【PA0 默认为高电平,当按键按下时,将 PA0 拉低到 GND】
- 原因:这样当按键没按下时,内部电阻会把 PA0 稳定在 3.3V(高电平),防止引脚“浮空”导致电平乱跳。
2. 代码实现
在 main.c 的 while(1) 中编写:
|
|
实验二:外部中断(EXTI)
轮询扫描很占 CPU 资源。中断 则像“报警器”:CPU 平时去干别的事,按键一按下,硬件自动触发一个信号通知 CPU 来处理。
1. CubeIDE 配置 (ioc 文件)
- PA0: 修改为
GPIO_EXTI0。 - GPIO 设置:
- GPIO mode: 选择 External Interrupt Mode with Falling edge trigger(下降沿触发,即电平从 1 变 0 的瞬间触发)。
- GPIO Pull-up/Pull-down: 保持 Pull-up。
- NVIC 设置 (最重要!):
- 点击左侧 System Core -> NVIC。
- 勾选 EXTI line0 interrupt 后面的 Enabled 框。如果不勾,中断永远不会发生!
- ![[Pasted image 20251220204306.png]]
为什么勾选 EXTI line 0? “EXTI line 0” 其实是 STM32 中断分配机制中的一个“频道”。STM32 的 GPIO 引脚非常多(PA0
PA15, PB0PB15…),但中断线(Line)资源是有限的
- 对应关系:引脚的编号决定了它属于哪条线。
- PA0, PB0, PC0, PD0 … 全部连接到 EXTI Line 0。
- PA1, PB1, PC1, PD1 … 全部连接到 EXTI Line 1。
- 排他性(重要):虽然 PA0 和 PB0 都能连到 Line 0,但同一时刻 Line 0 只能选择其中一个引脚。你不能同时把 PA0 和 PB0 都设为中断。
2. 代码实现
中断不需要写在 while(1) 里。HAL 库会自动调用一个名为“回调函数”的地方。你只需要在 main.c 的末尾(USER CODE BEGIN 4 之后)加上这个函数:
|
|
对比
两种方法的深度对比
| 特性 | 轮询扫描 (Polling) | 外部中断 (EXTI) |
|---|---|---|
| CPU 占用 | 高(一直在跑判断语句) | 极低(平时 CPU 可以休眠) |
| 实时性 | 取决于 while 循环里其他代码的长短 |
极高(信号一到立刻响应) |
| 消抖处理 | 简单(用 HAL_Delay) |
复杂(中断里不建议用 Delay,通常用定时器) |
| 适用场景 | 简单按键、对实时性要求不高 | 紧急停止、脉冲计数、低功耗唤醒 |
常见坑点(避坑指南)
- 忘记开 NVIC 中断开关:这是 EXTI 实验失败的 90% 原因。
- 中断里加了很长的延时:中断服务函数要求“快进快出”,如果在里面写
HAL_Delay(1000),系统可能会卡死或错过其他重要任务。 - 引脚悬空:如果不配置内部上拉(Pull-up),你的灯可能会因为你手指靠近板子而自己乱闪。
扩展资料:常见中断类型
STM32 的中断分为三大类:系统异常、外部中断 (EXTI) 和 外设中断
A. 系统异常 (System Exceptions)
这些是内核自带的,优先级极高,通常用于处理“紧急事故”。
- Reset: 复位中断。
- HardFault: 硬故障(程序崩溃、访问非法内存)。
- SysTick: 系统滴答定时器(HAL_Delay 的基础)。
B. 外部中断 (EXTI)
主要处理引脚的电平变化。Line 0 到 Line 15: 对应 GPIO 的 16 个引脚编号。
C. 外设中断 (Peripheral Interrupts)
每个功能模块都有自己的“报警器”。
- TIM (Timer): 定时器时间到、捕获到信号。
- UART/USART: 收到数据了、数据发完了。
- ADC: 模数转换完成了。
- DMA: 大批量数据搬运完了。
中断结构树图
graph TD
subgraph "中断源 (Interrupt Sources)"
subgraph "外部中断 EXTI"
EXTI0[EXTI Line 0
PA0/PB0/PC0...] EXTI1[EXTI Line 1
PA1/PB1/PC1...] EXTIn[EXTI Line 10-15] end subgraph "通信外设" UART[UART1/2
接收完成/发送空] I2C[I2C/SPI
传输错误/完成] end subgraph "定时与控制" TIM[TIM1/2/3/4
更新/比较/捕获] ADC[ADC
转换结束] end subgraph "系统异常 (内核)" SysTick[SysTick
1ms 节拍] Fault[HardFault
系统崩溃] end end EXTI0 --> NVIC EXTI1 --> NVIC EXTIn --> NVIC UART --> NVIC I2C --> NVIC TIM --> NVIC ADC --> NVIC SysTick --> NVIC Fault --> NVIC subgraph "NVIC (分诊台)" NVIC{NVIC 控制器
判断优先级/屏蔽} end NVIC -->|打断当前任务| CPU[Cortex-M3 内核] style CPU fill:#f96,stroke:#333,stroke-width:2px style NVIC fill:#bbf,stroke:#333
PA0/PB0/PC0...] EXTI1[EXTI Line 1
PA1/PB1/PC1...] EXTIn[EXTI Line 10-15] end subgraph "通信外设" UART[UART1/2
接收完成/发送空] I2C[I2C/SPI
传输错误/完成] end subgraph "定时与控制" TIM[TIM1/2/3/4
更新/比较/捕获] ADC[ADC
转换结束] end subgraph "系统异常 (内核)" SysTick[SysTick
1ms 节拍] Fault[HardFault
系统崩溃] end end EXTI0 --> NVIC EXTI1 --> NVIC EXTIn --> NVIC UART --> NVIC I2C --> NVIC TIM --> NVIC ADC --> NVIC SysTick --> NVIC Fault --> NVIC subgraph "NVIC (分诊台)" NVIC{NVIC 控制器
判断优先级/屏蔽} end NVIC -->|打断当前任务| CPU[Cortex-M3 内核] style CPU fill:#f96,stroke:#333,stroke-width:2px style NVIC fill:#bbf,stroke:#333
常见的 EXTI 线路“拼车”现象
在 STM32F103C8T6 中,为了节省硬件资源,有些中断线路是合并在一起的。你在 CubeIDE 的 NVIC 列表里会发现:
- EXTI0, EXTI1, EXTI2, EXTI3, EXTI4:这 5 条线是独立的,每个都有自己的中断服务函数。
- EXTI9_5_IRQHandler:Line 5 到 Line 9 的中断共享这一个函数。
- EXTI15_10_IRQHandler:Line 10 到 Line 15 的中断共享这一个函数。
这意味着:如果 PA10 和 PA11 都开启了中断,它们会跳进同一个函数。你需要在这个函数里通过 if 语句判断到底是哪条线触发的。