☰
课时02 嵌入式技术应用设计实训 | GPIO 输入与外部中断:按键
2026/10/5 8:57:44 网站建设 项目流程

嵌入式技术应用设计实训 | GPIO 输入与外部中断:按键

本课程开源地址(Gitee):https://gitee.com/fujianxinxi/qianrushishixundianzi.git
课件、示例代码与验收脚本都在该仓库,可直接git clone或下载 ZIP 使用。

B站配套视频合集:https://space.bilibili.com/572740825/lists?sid=9228122
合集《嵌入式技术应用设计实训|STM32F103 从点灯到智能家居》:每课配一节视频,可先看现象演示,再照着做。

开发板资料A盘(百度网盘):https://pan.baidu.com/s/1_EXrjRUewwmGMgcy38zEZg(提取码:zbri)
正点原子官方资料下载:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f103_nano.html

模块一 基础外设开发 · 2 学时 · 环境:Keil MDK5 + VS Code + 正点原子 STM32F103 Nano V1.3

一句话概述:从“读一个引脚的电平”入手,先弄清板载 4 个按键(KEY0/KEY1/KEY2/WK_UP)按下时分别是低电平还是高电平、为什么必须配上拉或下拉;再把它们配成外部中断(EXTI)+ NVIC,写出中断入口与回调,实现“按任意一个键,8 个 LED 在全亮与全灭之间翻转一次”。重点讲清“引脚号 → EXTI 线号 → NVIC 通道号”三段绑定,以及“中断里不能延时、消抖改用时间戳锁定”。课堂练习 4 条,大作业把“一键翻转全部”扩成“4 个键各管一个灯、按一下亮再按一下灭”。

本节目标

  1. 能说出读一个按键要按“开时钟 → 配输入(上拉/下拉)→ 读电平”三步走,并说清 KEY0/KEY1/KEY2 为什么配上拉、WK_UP 为什么配下拉
  2. 会配置 EXTI + NVIC(含中断入口函数与回调函数),让按任意一个键都能把 8 个 LED 在“全亮 / 全灭”之间翻转一次
  3. 能说出“引脚号 → EXTI 线号 → NVIC 通道号”的三段对应关系,并解释为什么中断里不能延时消抖

一、原理说明

GPIO 做输出是往外写电平,做输入就反过来——读电平:先给端口开时钟,再把引脚配成输入、并按需要打开上拉或下拉电阻,最后用HAL_GPIO_ReadPin()读回它的高低。板载 4 个按键并不是“按键直接接在 IO 上”这么简单:KEY0/KEY1/KEY2 是按键另一端接地,所以必须打开内部上拉,不按时引脚被拉成高电平、按下才被按键拉到地(低电平);WK_UP 接的是 3.3V,必须打开内部下拉,不按时是低、按下才是高——这也是 4 个键里唯一的反相。机械按键在按下和松开的一瞬间,触点会弹跳三五毫秒,电平像下雨一样忽高忽低,所以“读一次就当按下”往往会把一次按动读成好几次。外部中断(EXTI)的作用是让引脚自己盯着电平变化:变化一发生,硬件立刻把 CPU 拉到我们写好的那段函数里执行,主循环就不用一直盯着按键了。配一个外部中断要记住三段绑定——引脚号决定 EXTI 线号,EXTI 线号决定 NVIC 里的中断通道号,三段错一个,代码照样能编译、能下载,但按键就是一点反应都没有。

二、讲解内容

2.1 知识讲解

先认清 4 个按键接在哪、按下是什么电平(以下照厂家例程HARDWARE/KEY/key.c抄,别看反):

板载按键开发板引脚不按时按下时引脚怎么配(HAL)
KEY0PC8高(内部上拉)低GPIO_PULLUP+ 下降沿触发
KEY1PC9高(内部上拉)低同上(和 KEY0 一次配好)
KEY2PD2高(内部上拉)低同上(只换引脚号)
WK_UPPA0低(内部下拉)高(唯一反相)GPIO_PULLDOWN+ 上升沿触发

三段绑定:引脚号 → EXTI 线号 → NVIC 通道号。EXTI 的线号不看端口(PA/PC/PD 都一样),只看引脚号:PC8 是线 8、PC9 是线 9、PD2 是线 2、PA0 是线 0。而 NVIC 里的通道是按“线”分的,并且会合并——线 0、线 1 各有独立通道,线 5~9 挤在同一个通道EXTI9_5里:

按键引脚EXTI 线号中断入口函数(向量表按函数名找)NVIC 通道
WK_UPPA0线 0EXTI0_IRQHandlerEXTI0_IRQn
KEY2PD2线 2EXTI2_IRQHandlerEXTI2_IRQn
KEY0PC8线 8EXTI9_5_IRQHandlerEXTI9_5_IRQn
KEY1PC9线 9同上(和 PC8 同一个入口)同上

“线 8、线 9 共用一个入口”是新手最容易迷糊的地方:KEY0 和 KEY1 的中断都会跳进EXTI9_5_IRQHandler,所以在里面一次要把两个引脚都处理掉——本课直接写成HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8 | GPIO_PIN_9);,谁触发就翻谁的状态,写法最简单也最不容易漏。

输入三步,和输出三步一样不能少:

步骤干什么本课的代码不做会怎样
① 开时钟给按键引脚所在端口使能时钟__HAL_RCC_GPIOA_CLK_ENABLE();等三行引脚配置全部无效,按键没反应,且不报错
② 配模式配成输入并按需要打开上拉/下拉;要中断就再加上触发边沿GPIO_MODE_IT_FALLING+GPIO_PULLUP引脚是复位后的浮空输入,电平乱飘,会误触发
③ 使能中断让 NVIC 放行这条中断通道HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);引脚能看到电平变化,但 CPU 永远不会被叫去处理

STM32F1 的一个专属细节(AFIO 时钟):F1 的 EXTI 引脚映射要经过 AFIO 外设,写寄存器版时得先__HAL_RCC_AFIO_CLK_ENABLE();。不过 HAL 库的HAL_GPIO_Init()一看到IT模式就替你把这句写好了(在stm32f1xx_hal_gpio.c里),所以本课代码里不用再写;看到别的例程多这一句,知道它在干什么就行。

抖动与消抖:两种做法,效果一样,代价不同。机械按键按下和松开的瞬间,触点在几毫秒里反复通断,电平会跳好几次——这就是“抖动”。不处理的话,一次按动会被当成两三次:

做法怎么写优点代价
轮询 + 延时消抖厂家例程的KEY_Scan():读到按下 →delay_ms(10)再确认一次 → 松手后才允许下一次直观好懂,适合小实验CPU 一直盯着按键;delay_ms那 10ms 里什么都干不了
中断 + 时间戳锁定(本课用)回调里记HAL_GetTick(),200ms 内再来就丢掉不占 CPU,主循环空闲快速连按会被“吞”掉几次(把KEY_LOCK_MS调小可缓解)

中断里为什么不能延时:中断一旦进入,主循环就停在原地等它执行完。如果在中断里写delay_ms(10),整个系统就被卡住 10ms,按键按得急了还会互相插队,现象会变得莫名其妙。另外HAL_GetTick()靠 SysTick 的 1ms 中断累计,如果中断优先级配得不对,它可能一直不变,用时间戳会算错。本课把 EXTI 的抢占优先级设成2(数值越大优先级越低),而 SysTick 是 0、比它高,所以在中断里HAL_GetTick()照样在走——这是“时间戳锁定”能放进中断的前提。

HAL 库的中断调用链(记住这三步就够):

引脚电平出现下降沿/上升沿(KEY0~KEY2 下降沿、WK_UP 上升沿) │ 硬件自动触发 ▼ EXTI9_5_IRQHandler() ← 中断入口函数,向量表按这个名字找它 │ 我们调用 ▼ HAL_GPIO_EXTI_IRQHandler(引脚) ← HAL 帮你清掉中断挂起标志 │ 它再调用 ▼ HAL_GPIO_EXTI_Callback(引脚) ← 我们干活的地方:翻转 8 个灯

HAL_GPIO_EXTI_Callback()在 HAL 库里是弱函数(__weak):我们在main.c里重写一份,链接时就会用我们这份,不用注册、也不用函数指针。

2.2 代码示例

本课只改一个文件:课时文档/代码/课时02-GPIO 输入与外部中断-按键/按键实验/USER/main.c。工程里其余文件都是厂家例程现成的,不用动:

课时文档/代码/课时02-GPIO 输入与外部中断-按键/ └── 按键实验/ ← 正点原子官方“按键实验”例程(HAL 库版) ├── USER/KEY.uvprojx ← Keil 打开这个工程 ├── USER/main.c ← 本课唯一要改的文件 ├── HARDWARE/KEY/ ← 厂家按键驱动 key.c / key.h(轮询版,本课不调用,练习 P2.3 要用) ├── HARDWARE/LED/ ← LED 驱动:PC0~PC7 配成推挽输出,上电默认全灭 ├── HARDWARE/BEEP/ ← 蜂鸣器驱动(本课不用,练习 P2.4 会用到) ├── SYSTEM/ ← sys、delay(delay_ms 就在这里)、usart ├── HALLIB/ CORE/ ← HAL 库与启动文件(不用改) └── OBJ/ ← 编译输出(已加入 .gitignore,不进仓库)

下面是本课USER/main.c的完整代码(一个文件,一个现象):

#include"sys.h"#include"delay.h"#include"usart.h"#include"led.h"/************************************************ 嵌入式技术应用设计实训 课时02 GPIO 输入与外部中断:按键 本文件基于正点原子 ALIENTEK NANO STM32F103 厂家例程(按键实验-HAL库版)改写。 例程版权归广州市星翼电子科技有限公司所有。 硬件(正点原子 STM32F103 Nano V1.3,全部板载,不用接线): DS0~DS7(红色 LED) 接 PC0~PC7,低电平点亮 KEY0 接 PC8、KEY1 接 PC9、KEY2 接 PD2 —— 按下是低电平(芯片内上拉) WK_UP 接 PA0 —— 按下是高电平(芯片内下拉,唯一反相) 本课现象(只有一个):按任意一个键,8 个 LED 在“全亮 / 全灭”之间翻转一次。 实现方式:外部中断(EXTI + NVIC)。中断只做两件事——判断抖动、翻转灯, 其余时间主循环什么都不干,等着中断来敲门。 两个必须记住的坑: 1) 中断里不能延时消抖(delay_ms 靠 SysTick,在中断里死等会把整个程序拖住), 所以本课用“时间戳锁定”:200ms 内的重复触发直接丢掉。 2) 中断入口函数 EXTIx_IRQHandler 必须存在,否则中断触发了也进不来。 厂家这个例程是轮询版,没写这三个入口,本课把它们补在下面。 ************************************************//* ---------- LED:低电平点亮,PC0~PC7 对应 DS0~DS7(与课时01 写法一致) ---------- */#defineLED_ALL_OFF()HAL_GPIO_WritePin(GPIOC,(uint16_t)0x00FF,GPIO_PIN_SET)/* 8 个灯全灭 */#defineLED_ALL_ON()HAL_GPIO_WritePin(GPIOC,(uint16_t)0x00FF,GPIO_PIN_RESET)/* 8 个灯全亮 *//* ---------- 消抖:同一个键两次响应的最小间隔(毫秒),觉得太钝就改小 ---------- */#defineKEY_LOCK_MS200staticu8 all_on=0;/* 8 个灯当前的状态:0 = 全灭,1 = 全亮 *//* ------------------------------------------------------------------ 中断入口:Cortex-M3 的向量表按函数名找入口,名字写错 = 中断永远进不来 PA0 -> EXTI0 线 0 PD2 -> EXTI2 线 2 PC8 -> EXTI9_5 线 8、线 9 共用一个入口,所以一次要处理两个引脚 HAL_GPIO_EXTI_IRQHandler() 会清中断标志,再调用下面的 HAL_GPIO_EXTI_Callback() ------------------------------------------------------------------ */voidEXTI0_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);/* WK_UP */}voidEXTI2_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);/* KEY2 */}voidEXTI9_5_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8|GPIO_PIN_9);/* KEY0 / KEY1 */}/* ------------------------------------------------------------------ 按键中断初始化:4 个按键全部配成“外部中断”模式 输入三步一样不能少:开时钟 -> 配模式(含上拉/下拉、触发边沿)-> 使能中断 注意 WK_UP 与另外三个相反:按下是高电平,所以配下拉 + 上升沿 ------------------------------------------------------------------ */staticvoidKEY_EXTI_Init(void){GPIO_InitTypeDef GPIO_Initure;__HAL_RCC_GPIOA_CLK_ENABLE();/* WK_UP 在 PA0 */__HAL_RCC_GPIOC_CLK_ENABLE();/* KEY0/KEY1 在 PC8/PC9 */__HAL_RCC_GPIOD_CLK_ENABLE();/* KEY2 在 PD2 *//* STM32F1 的 EXTI 引脚映射要经过 AFIO:HAL 库的 HAL_GPIO_Init() 看到 IT 模式时 已经替你开了 AFIO 时钟(stm32f1xx_hal_gpio.c 里那行 __HAL_RCC_AFIO_CLK_ENABLE()), 所以这里不用再写;写寄存器版时要自己开。 *//* KEY0(PC8)、KEY1(PC9):按下是低电平 -> 上拉 + 下降沿 */GPIO_Initure.Pin=GPIO_PIN_8|GPIO_PIN_9;GPIO_Initure.Mode=GPIO_MODE_IT_FALLING;GPIO_Initure.Pull=GPIO_PULLUP;GPIO_Initure.Speed=GPIO_SPEED_HIGH;HAL_GPIO_Init(GPIOC,&GPIO_Initure);/* KEY2(PD2):同上(沿用上面的结构体,只改引脚) */GPIO_Initure.Pin=GPIO_PIN_2;HAL_GPIO_Init(GPIOD,&GPIO_Initure);/* WK_UP(PA0):按下是高电平 -> 下拉 + 上升沿,触发方式与上面相反 */GPIO_Initure.Pin=GPIO_PIN_0;GPIO_Initure.Mode=GPIO_MODE_IT_RISING;GPIO_Initure.Pull=GPIO_PULLDOWN;HAL_GPIO_Init(GPIOA,&GPIO_Initure);/* 使能 NVIC 通道:引脚号 -> EXTI 线号 -> 中断通道号,三段要一一对上 */HAL_NVIC_SetPriority(EXTI0_IRQn,2,0);/* 线 0,WK_UP */HAL_NVIC_EnableIRQ(EXTI0_IRQn);HAL_NVIC_SetPriority(EXTI2_IRQn,2,0);/* 线 2,KEY2 */HAL_NVIC_EnableIRQ(EXTI2_IRQn);HAL_NVIC_SetPriority(EXTI9_5_IRQn,2,0);/* 线 5~9,KEY0/KEY1 都在这 */HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);}/* ------------------------------------------------------------------ 中断回调:HAL 库里所有 EXTI 线最终都进这里 它在 HAL 里是“弱函数”,在 main.c 里重写一份就被调用(不用注册) 这里只干两件事:200ms 锁定消抖、翻转 8 个灯 ------------------------------------------------------------------ */voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){staticuint32_tlast_tick=0;/* 上一次响应的时间戳 */uint32_tnow=HAL_GetTick();/* 开机以来的毫秒数 */if(now-last_tick<KEY_LOCK_MS)return;/* 200ms 内再触发:判为抖动,丢掉 */last_tick=now;all_on=!all_on;/* 翻转状态 */if(all_on)LED_ALL_ON();elseLED_ALL_OFF();}intmain(void){HAL_Init();/* 初始化 HAL 库:顺便把 1ms 时基(HAL_GetTick)跑起来 */Stm32_Clock_Init(RCC_PLL_MUL9);/* 系统时钟:倍频到 72MHz */delay_init(72);/* 初始化延时函数 */LED_Init();/* 初始化 LED:DS0~DS7(PC0~PC7) */KEY_EXTI_Init();/* 初始化 4 个按键的外部中断 */LED_ALL_OFF();/* 上电先全灭,按下第一个键就看到“全亮” */while(1){/* 主循环什么都不用做:按键一按,中断自己把 8 个灯翻转 (这就是中断的好处——CPU 不用一直盯着按键) */}}

代码里最值得盯住的 4 处:

位置为什么这么写
GPIO_MODE_IT_FALLING/GPIO_MODE_IT_RISINGKEY0~KEY2 按下是低电平,配下降沿;WK_UP 按下是高电平,配上升沿。配反的现象是“松手才翻”或“完全没反应”
EXTI9_5_IRQHandler()里的GPIO_PIN_8与GPIO_PIN_9线 8、线 9 共用一个入口,两个按键一次处理完,少写一个就会“KEY1 按下没反应”
HAL_GPIO_EXTI_Callback()里的HAL_GetTick()判断中断里不能延时,用时间戳做 200ms 锁定消抖
while (1) {}空着的主循环说明“中断把主循环解放了”:按键响应不再靠轮询,主循环可以什么都不干

三、实验操作与现象

3.1 接线与操作步骤

本课不用任何额外接线:LED、4 个按键都在板上(这点和课时01 一样,插上就能做)。

模块/元件开发板引脚说明
KEY0PC8按下为低电平;已打开内部上拉,不用外接电阻
KEY1PC9同上(与 KEY0 共用中断入口)
KEY2PD2同上(独立一条 EXTI 线)
WK_UPPA0按下为高电平;已打开内部下拉(唯一反相,别照 KEY0 的写法套)
DS0~DS7PC0~PC7低电平点亮,本课用来显示按键状态
ST-LinkSWDIO / SWCLK / GND下载与调试(课时00 已连好)

操作步骤:

  1. Keil 打开工程:课时文档/代码/课时02-GPIO 输入与外部中断-按键/按键实验/USER/KEY.uvprojx
  2. 选下载器:Options for Target → Debug → ST-Link Debugger → OK(课时00 已设过,换电脑要重设)
  3. 看代码:确认main.c里的KEY_LOCK_MS是200(消抖锁定时间)
  4. 编译:Build(F7),确认0 Error(s), 0 Warning(s)
  5. 下载:Download(F8),确认Programming Done. Verify OK.
  6. 按复位键 RST(或重新上电):8 个灯全灭,程序就绪
  7. 依次按 KEY0、KEY1、KEY2、WK_UP:每按一次,8 个灯在“全亮 / 全灭”之间翻转一次

3.2 观察到的现象

本课现象以随课视频演示为准(视频里会把 4 个按键各按几遍、再快速连按一遍),文档不逐条配截图;你自己做完请录一段或拍一张,作为提交物。

操作看到什么算成功怎么核对
上电/复位后8 个灯全灭,安静地等着一个灯都不亮,也没有声音
按一下 KEY0(PC8)8 个灯全部亮;再按一下全部灭一次按动只翻转一次,不会只亮一部分
按一下 KEY1(PC9)同上KEY1 与 KEY0 走同一个中断入口EXTI9_5,现象应完全一致
按一下 KEY2(PD2)同上它走EXTI2,是独立的一条线
按一下 WK_UP(PA0)同上它是高电平有效、走EXTI0;松开时不应再翻一次
快速连按同一个键(每秒 4~5 下)灯不会被一次按动翻两三次200ms 锁定在起作用;把KEY_LOCK_MS改成500再按,感觉更明显

看不到现象怎么办(按最可能的原因排序):

现象最可能的原因怎么处理
4 个键按了都没反应,灯一直全灭中断入口函数没写或名字拼错(例如写成EXIT9_5_IRQHandler);或忘了HAL_NVIC_EnableIRQ()对照 2.1 节的“三段绑定”表,逐项核对入口函数名与通道号
只有 WK_UP 有反应,另外三个没反应KEY0/KEY1/KEY2 的配置不对:漏了GPIO_PULLUP,或漏开 GPIOC / GPIOD 时钟检查三行__HAL_RCC_GPIOx_CLK_ENABLE()是否都在
只有 KEY0 有反应,KEY1 没反应EXTI9_5_IRQHandler里只写了GPIO_PIN_8补成GPIO_PIN_8与GPIO_PIN_9
按键一按,灯闪两三次才停消抖没生效(KEY_LOCK_MS被改成 0)恢复KEY_LOCK_MS 200
松手才翻 / 按住不翻触发边沿配反了:低有效的键配成了上升沿KEY0~KEY2 用GPIO_MODE_IT_FALLING,WK_UP 用GPIO_MODE_IT_RISING
编译报重复定义(multiply defined)中断入口函数在stm32f1xx_it.c里也写了一份(本工程没有,换别的例程时可能遇到)只保留一处;本课统一写在main.c
一上电 8 个灯就是全亮少了LED_ALL_OFF()那句在LED_Init()之后补上
下载报No Cortex-M Device found下载器没连好 / 板子没上电查 SWD 三根线与供电(课时00 练过一遍)

四、学生练习

课堂上按顺序做,一条一条改、一条一条看现象:

编号任务提示预期结果
P2.1只让 KEY0 有效注释掉__HAL_RCC_GPIOA_CLK_ENABLE();、__HAL_RCC_GPIOD_CLK_ENABLE();,以及 WK_UP、KEY2 的HAL_GPIO_Init()与两次HAL_NVIC_EnableIRQ()(EXTI0、EXTI2)只有 KEY0 能翻转 8 个灯,另外三个键按了没反应;EXTI9_5里只处理线 8 也行
P2.2亲眼看抖动把KEY_LOCK_MS改成0,快速连按同一个键 10 次;看完再改回200改 0 时灯会多翻好几下(一次按动翻两三次),改回 200 后恢复正常
P2.3改成轮询版用工程自带的HARDWARE/KEY/key.c:while (1)里key = KEY_Scan(0);判断KEY0_PRES~WKUP_PRES;去掉 NVIC 配置与三个中断入口函数按键照样能翻转灯;再把KEY_Scan里的delay_ms(10)注释掉,快速按几下看会不会变“脏”
P2.4加一声“嘀”(选做)本板蜂鸣器接 PB8、低电平响(课时01 已实测):按键时HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET),100ms 后写回GPIO_PIN_SET每按一次“嘀”一声,同时 8 个灯翻转;注意别在中断里用delay_ms等 100ms
P2.5挑战:把边沿改成双边沿把 WK_UP 配成GPIO_MODE_IT_RISING_FALLING(下拉不变),按住、松开各看一次按住翻一次、松开又翻一次——直观体会“边沿触发”与“电平”的差别

五、大作业(实验二:4按键控制 LED 亮灭)

本作业对应课程大纲里的实验二「4按键控制 LED 亮灭」,当堂完成、当堂验收;做不完的带回宿舍接着做,下次课开课先收。

5.1 任务书

把课上“任意一个键按下,8 个灯一起翻转”扩成“4 个键各管一个灯,按一下亮、再按一下灭”,4 个灯互不影响:

  1. 必须用本课学的外部中断(EXTI + NVIC)实现,处理都在HAL_GPIO_EXTI_Callback()里
  2. 每盏灯的状态要用变量记住(例如static u8 led_on[4];)——不要“读灯现在的电平再取反”,那是另一种写法,键与灯不是一对一的时候会立刻出错
  3. 键与灯的对应:KEY0 → DS0、KEY1 → DS1、KEY2 → DS2、WK_UP → DS3;其余四个灯(DS4~DS7)保持全灭
  4. 保留 200ms 锁定消抖,同一个键连按 10 次,该灯亮灭各 5 次
  5. 编译输出0 Error(s), 0 Warning(s)

5.2 分步提示

  1. 先想清“谁管谁”:GPIO_PIN_8(KEY0)→ 第 0 号灯,GPIO_PIN_9(KEY1)→ 第 1 号灯,GPIO_PIN_2(KEY2)→ 第 2 号灯,GPIO_PIN_0(WK_UP)→ 第 3 号灯
  2. 回调里先认出是哪个键:用GPIO_Pin判断,得到一个序号n = 0~3
  3. 用数组记状态:static u8 led_on[4] = {0, 0, 0, 0};,翻转led_on[n] = !led_on[n];
  4. 按状态写电平:HAL_GPIO_WritePin(GPIOC, (uint16_t)(1U << n), led_on[n] ? GPIO_PIN_RESET : GPIO_PIN_SET);——本板低电平点亮,别写反
  5. 编译下载,4 个键各按两轮,确认互不干扰;再做 P2.2 那样的快速连按,确认不会多翻

HAL_GPIO_EXTI_Callback()改写后的样子(关键部分,其余保持课上代码不变):

staticu8 led_on[4]={0,0,0,0};/* DS0~DS3 各自的状态:0 = 灭,1 = 亮 */voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){staticuint32_tlast_tick=0;uint32_tnow=HAL_GetTick();u8 n;if(now-last_tick<KEY_LOCK_MS)return;/* 消抖:200ms 内不重复响应 */last_tick=now;if(GPIO_Pin==GPIO_PIN_8)n=0;/* KEY0 -> DS0 */elseif(GPIO_Pin==GPIO_PIN_9)n=1;/* KEY1 -> DS1 */elseif(GPIO_Pin==GPIO_PIN_2)n=2;/* KEY2 -> DS2 */elsen=3;/* WK_UP -> DS3 */led_on[n]=!led_on[n];/* 翻转这个灯的状态 */HAL_GPIO_WritePin(GPIOC,(uint16_t)(1U<<n),led_on[n]?GPIO_PIN_RESET:GPIO_PIN_SET);/* 低电平点亮 */}

5.3 现象与验收标准

验收项标准
单键按一下 KEY0:DS0 亮;再按一下:DS0 灭
互相独立4 个键各按两轮,4 个灯各自亮灭,互不影响(DS1 亮着时按 KEY0,DS1 不受影响)
连按同一个键连按 10 次,该灯亮灭各 5 次,不多翻、不跳
消抖按得快、按得慢,都不出现“一次按动翻两次”
其余灯DS4~DS7 全程全灭
编译下载0 Error(s), 0 Warning(s);Programming Done. Verify OK.

5.4 提交物

  • 代码:课时文档/代码/课时02-GPIO 输入与外部中断-按键/按键实验/USER/main.c
  • 现象:一段 20 秒以内的视频(4 个键各按两轮,能让老师看清“哪个键亮了哪个灯”),或按动前后的两张照片
  • 一句话说明:“灯的状态记在哪个变量里?为什么不能靠读灯的电平再取反来做?”

六、本节小结与下节预告

本节把 GPIO 输入这条线完整走了一遍:4 个按键接在哪、按下是高还是低、为什么必须配上拉或下拉(KEY0/KEY1/KEY2 按下是低、WK_UP 按下是高),再用外部中断把它接到 CPU 上——HAL_GPIO_Init()配成GPIO_MODE_IT_FALLING/..._RISING、HAL_NVIC_EnableIRQ()放行通道、EXTIx_IRQHandler()当入口、HAL_GPIO_EXTI_Callback()干活,最后只剩一句“翻转 8 个灯”。三句话记住本课:电平配反了,现象就拧着来(按住才响应、或松手才翻);中断有三段绑定(引脚号 → EXTI 线号 → NVIC 通道号),缺一段就是“编译没报错、按键没反应”;中断里要短,消抖用时间戳锁定,绝不在中断里delay_ms。下节进入串口通信:把板子里的信息打到电脑上看(USART + printf 重定向)。

本节函数与宏速查

名称一句话作用
__HAL_RCC_GPIOA_CLK_ENABLE()等给按键所在端口开时钟(PA0 / PC8 / PC9 / PD2 三个端口)
HAL_GPIO_Init(端口, &结构体)配成外部中断模式:GPIO_MODE_IT_FALLING/..._RISING+ 上拉/下拉(F1 上它会自动开 AFIO 时钟)
HAL_GPIO_ReadPin(端口, 引脚)读引脚电平(轮询版用,厂家key.c里就是这么读的)
HAL_NVIC_SetPriority(通道, 抢占, 子)给中断排优先级(本课统一用 2,数值越大越低)
HAL_NVIC_EnableIRQ(通道)放行通道:EXTI0_IRQn/EXTI2_IRQn/EXTI9_5_IRQn
EXTIx_IRQHandler()中断入口函数(向量表按名字找它),里面调HAL_GPIO_EXTI_IRQHandler(引脚)
HAL_GPIO_EXTI_IRQHandler(引脚)HAL 提供的“清中断标志 + 转回调”
HAL_GPIO_EXTI_Callback(引脚)弱函数,我们在main.c里重写:消抖与翻转灯都在这里
HAL_GetTick()开机以来的毫秒数(1ms 时基),本课用它做 200ms 锁定消抖
LED_ALL_ON()/LED_ALL_OFF()8 个灯一起亮 / 一起灭(沿用课时01 的写法)

附录

A. 怎么编译下载(照着做就能跑)

1. 上电接线:板子 USB 供电 → ST-Link 接 SWD 三根线(SWDIO / SWCLK / GND)→ ST-Link 插电脑 2. Keil 打开:课时文档/代码/课时02-GPIO 输入与外部中断-按键/按键实验/USER/KEY.uvprojx 3. 选下载器:Options for Target → Debug → ST-Link Debugger → OK 4. 看参数 :main.c 开头的 #define KEY_LOCK_MS 200(消抖锁定时间,改小=响应快但容易多翻) 5. 编译 :Build(F7),确认 0 Error(s), 0 Warning(s) 6. 下载 :Download(F8),确认 Programming Done. Verify OK. 7. 复位 :按 RST 键,8 个灯全灭;按任意一个键,8 个灯在全亮 / 全灭之间翻转

B. 本课代码

本课只改一个文件:课时文档/代码/课时02-GPIO 输入与外部中断-按键/按键实验/USER/main.c(完整代码见 2.2 节)。工程里其余文件都是厂家例程现成的,不用动:

文件作用
USER/KEY.uvprojxKeil 工程文件(用 Keil 打开这个)
USER/main.c本课自写代码:按键 EXTI 初始化 + 三个中断入口 + 中断回调
USER/stm32f1xx_it.c中断入口的“官方住处”;本课三个 EXTI 入口写在main.c里,这个文件不用改
HARDWARE/KEY/key.c、key.h厂家按键驱动(轮询版):KEY_Init()/KEY_Scan()/KEY0_PRES等,练习 P2.3 要用
HARDWARE/LED/led.c、led.hLED 驱动:PC0~PC7 配成推挽输出并上电置全灭
HARDWARE/BEEP/beep.c、beep.h蜂鸣器驱动(PB8),练习 P2.4 要用
SYSTEM/sys、delay、usart(delay_ms在这里)
HALLIB/、CORE/STM32F1 HAL 库与启动文件
OBJ/编译输出(已加入.gitignore,不进仓库)

C. 引脚对照表(贴在板子旁边用)

引脚板载元件不按时 / 按下时说明
PC8KEY0高 /低内部上拉;按下翻转 8 个灯
PC9KEY1高 /低与 KEY0 共用中断入口EXTI9_5_IRQHandler
PD2KEY2高 /低独立中断入口EXTI2_IRQHandler
PA0WK_UP低 /高唯一反相;中断入口EXTI0_IRQHandler
PC0~PC7DS0~DS7——低电平点亮(课时01 已实测)
PB8蜂鸣器 BEEP——低电平响(课时01 已实测;练习 P2.4 用)

相关文档

  • 课程大纲(课时02 在“一、基础外设开发模块”)
  • 课程文档写作规范(0.1 教学环境约定)
  • 上一课:课时01 GPIO 输出:跑马灯与蜂鸣器
  • 下一课:课时03 串口通信:USART 收发与 printf

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询