嵌入式基础-按键检测代码解读
代码:
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
{
delay_ms(10);//去抖动
key_up=0;
if(KEY0==0)return KEY0_PRES;
else if(KEY1==0)return KEY1_PRES;
else if(WK_UP==1)return WKUP_PRES;
}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
return 0;// 无按键按下
}
下面逐行(或逐逻辑块)解释这段按键扫描函数 KEY_Scan(u8 mode) 的工作原理。它是典型的 嵌入式单片机(STM32 等)按键检测代码,实现了 消抖 + 优先级判断 + 可选连续按 功能。
函数功能总览
u8 KEY_Scan(u8 mode)
- 参数
mode:0:不支持连续按(一次按键只响应一次)1:支持连续按(长按会持续返回按键值)
- 返回值:
KEY0_PRES(通常为 1):KEY0 被按下KEY1_PRES(通常为 2):KEY1 被按下WKUP_PRES(通常为 3):WK_UP 被按下0:无按键按下
注意:按键优先级:KEY0 > KEY1 > WK_UP
关键变量
static u8 key_up = 1; // 按键松开标志(静态变量,函数调用间保持值)
key_up == 1:表示当前允许检测新按键(即上一次按键已释放)key_up == 0:表示正在处理一次按键中(避免重复触发)
代码逐段解析
if(mode) key_up = 1; // 支持连按
- 如果
mode == 1(支持连按),强制允许检测按键 - 即使之前检测到按下,也重置状态,允许持续触发
- 这是“连按”功能的核心
if(key_up && (KEY0==0 || KEY1==0 || WK_UP==1))
- 条件成立才进入检测:
key_up == 1(之前已松开)- 任意一个按键被按下(注意电平定义):
KEY0==0和KEY1==0:低电平有效(按下为 0)WK_UP==1:高电平有效(按下为 1,常见于上拉输入)
说明:不同按键使用不同电平逻辑,常见设计。
delay_ms(10); // 去抖动
- 检测到按键电平变化后,延时 10ms
- 目的是消除机械按键抖动(按下/松开时电平不稳定)
key_up = 0;
- 锁定状态:标记“正在处理一次按键”
- 防止在按键未松开时重复返回按键值(除非支持连按)
if(KEY0==0) return KEY0_PRES;
else if(KEY1==0) return KEY1_PRES;
else if(WK_UP==1)return WKUP_PRES;
- 优先级检测:
- 先查 KEY0
- 再查 KEY1
- 最后查 WK_UP
- 即使多个键同时按下,只返回最高优先级的那个
else if(KEY0==1 && KEY1==1 && WK_UP==0) key_up = 1;
- 所有按键都松开时:
KEY0==1(高)、KEY1==1(高)、WK_UP==0(低)
- 说明按键已释放 → 恢复检测状态
key_up = 1,下次按键可被再次识别
return 0; // 无按键按下
- 所有情况都不满足 → 返回 0
工作流程图解
调用 KEY_Scan(mode)
│
├── mode == 1 ? → key_up = 1(允许连按)
│
├── key_up == 1 且 有键按下?
│ YES
│ ├── delay 10ms 去抖
│ ├── key_up = 0(锁定)
│ ├── 按优先级返回:KEY0 → KEY1 → WK_UP
│
├── 所有键都松开?
│ YES → key_up = 1(解锁)
│
└── 其他情况 → return 0
两种模式行为对比
| 模式 | 行为 |
|---|---|
mode = 0(默认) |
单击响应:按住只返回一次,松开后才能再次触发 |
mode = 1 |
连按响应:长按会持续返回按键值(适合方向键、加速等) |
使用示例(主循环中)
u8 key;
key = KEY_Scan(0); // 不支持连按
if(key == KEY0_PRES) { /* 处理 KEY0 */ }
key = KEY_Scan(1); // 支持连按(如菜单快速移动)
if(key) { /* 持续处理 */ }
总结:函数特点
| 特性 | 说明 |
|---|---|
| 消抖 | 10ms 延时 |
| 优先级 | KEY0 > KEY1 > WK_UP |
| 支持连按 | mode=1 时实现 |
| 状态保持 | static key_up 记住按键状态 |
| 电平逻辑 | KEY0/KEY1 低有效,WK_UP 高有效 |
这是一段经典、健壮、实用的按键扫描函数,广泛用于 STM32、51 单片机等裸机开发中。
不明白为什么改变mode的值可以支持单击或者连按?
非常好的问题!你抓住了这个函数的核心机制:mode 到底是怎么控制“单击”和“连按”的?
我们来一步步拆开,用时间轴 + 状态变化的方式彻底说明白。
先搞清楚:什么是“单击响应”和“连按响应”?
| 行为 | 说明 |
|---|---|
| 单击响应(mode=0) | 按下 → 返回一次值 → 即使继续按着也不再返回 → 必须松开再按下才触发第二次 |
| 连按响应(mode=1) | 按下 → 持续返回按键值(每调用一次就返回一次)→ 适合“长按加速”、“方向键连发” |
关键变量:static u8 key_up = 1;
这是整个逻辑的**“记忆开关”**:
static u8 key_up = 1; // 1=可以检测新按键,0=正在按住中,禁止重复检测
key_up == 1:允许检测按键key_up == 0:禁止检测(防止重复触发)
现在分两种情况,画出时间轴来看 mode 的作用
情况1:mode = 0(不支持连按)—— 单击模式
if(mode) key_up=1; // mode=0 → 这行不执行
| 时间点 | 按键状态 | key_up | 检测条件 | 行为 |
|---|---|---|---|---|
| 初始 | 未按 | 1 | — | — |
| 按下 | KEY0=0 | 1 | key_up && KEY0==0 → 成立 |
→ 去抖 → key_up=0 → 返回 KEY0_PRES |
| 持续按住 | KEY0=0 | 0 | key_up && KEY0==0 → 不成立 |
→ 不返回任何值 |
| 松开 | KEY0=1 | 0 | 所有键松开 → key_up=1 |
恢复检测 |
| 再次按下 | KEY0=0 | 1 | 再次成立 | → 再次返回 KEY0_PRES |
结论:按住只触发一次,必须松开才能再触发 → 单击响应
情况2:mode = 1(支持连按)—— 连发模式
if(mode) key_up=1; // mode=1 → 每次调用都执行 key_up=1
| 时间点 | 按键状态 | key_up | 检测条件 | 行为 |
|---|---|---|---|---|
| 初始 | 未按 | 1 | — | — |
| 按下 | KEY0=0 | 1 | 成立 | → 去抖 → key_up=0 → 返回 KEY0_PRES |
| 下次调用 | 仍按着 | 被强制设为 1 | key_up && KEY0==0 → 成立 |
→ 去抖 → key_up=0 → 再次返回 |
| 持续调用 | 仍按着 | 每次被 mode=1 强置为 1 |
每次都成立 | → 持续返回按键值 |
结论:只要按着,每次调用都返回 → 连按响应
图解对比(关键区别就在这一行!)
if(mode) key_up=1; // 这行是“连按”的灵魂!
| mode | 这行执行吗? | key_up 是否被重置? | 能否连续检测? |
|---|---|---|---|
| 0 | 不执行 | 只在松开时恢复 | 不能(单击) |
| 1 | 每次都执行 | 每次调用都重置为1 | 能(连按) |
类比理解(生活例子)
| 模式 | 像什么? |
|---|---|
mode=0 |
相机快门:按一次,拍一张,按住不连拍 |
mode=1 |
游戏手柄:按住方向键,角色持续走 |
mode=1 就像 “强制解锁检测开关”,让系统“忘记”你上一次按过。
代码行为总结(一句话)
mode=1就是“每次调用前都把 key_up 强行设为 1”,从而绕过“必须松开才能再检测”的限制,实现连按!
验证实验(你可以自己试)
while(1)
{
key = KEY_Scan(1); // 改成 1
if(key) printf("Key = %d\n", key);
delay_ms(50); // 每50ms扫描一次
}
- 按住 KEY0 → 终端疯狂打印
Key = 1 - 改成
KEY_Scan(0)→ 只打印一次
最终结论
| 问题 | 答案 |
|---|---|
为什么改 mode 就能切换单击/连按? |
因为 mode=1 会在每次调用前强制 key_up=1,让检测逻辑“失忆”,从而允许重复触发 |
key_up 的作用是什么? |
防止按住时重复触发(防抖+防连按) |
mode=1 是怎么打破限制的? |
它绕过 key_up 的锁定机制,每次都强行“解锁” |
一句话总结:
mode=0:“按住锁死,松开解锁”mode=1:“每次都解锁,长按连发”
进入函数最开始不是有static u8 key_up=1;语句吗,那应该每次调用函数都设置key_up=1?
非常棒的问题!你已经抓住了 C语言中 static 变量的本质,但这里有一个关键误解:
错觉:
static u8 key_up=1;是不是每次调用函数都会执行,把key_up设为 1?
正确答案:不是!
核心知识点:static 变量的初始化 只执行一次
static u8 key_up = 1; // 这行代码,只在程序启动时执行一次!
详细解释:
| 阶段 | 发生什么 |
|---|---|
| 程序第一次运行到这行 | 分配内存,给 key_up 赋初值 1 |
| 之后每次调用函数 | 跳过这行初始化,key_up 保留上一次的值 |
这就是 static 的意义:“函数内的全局变量”,在多次调用间保持值不变。
用时间轴演示(假设 mode=0)
| 调用次数 | 执行 static u8 key_up=1; |
实际 key_up 值 |
说明 |
|---|---|---|---|
| 第1次 | 执行(初始化) | 1 |
程序刚开始 |
| 第2次 | 不执行 | 保留上次值 | static 跳过 |
| 第3次 | 不执行 | 保留上次值 | … |
举个生活类比
static int money = 100; // 你银行卡里的钱
- 第一次开户:存入 100 元 →
money = 100 - 之后每次来银行:
- 不会再给你 100 元!
- 你花了 50,剩下 50 → 下次来还是 50
- 除非你又存钱
static 就像“只初始化一次的内存盒子”
回到你的按键函数
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1; // 只在程序第一次运行时设为1
if(mode) key_up=1; // 每次调用都可能重置为1(关键!)
...
}
行为对比:
| mode | static 初始化 |
if(mode) 是否重置 |
最终效果 |
|---|---|---|---|
0 |
只第一次设1 | 不执行 | key_up 能被改为0并保持 → 防连按 |
1 |
只第一次设1 | 每次都设1 | key_up 永远是1 → 允许连按 |
模拟执行(mode=0)
// 第1次调用
static u8 key_up=1; // 执行 → key_up=1
if(0) key_up=1; // 不执行
...按下键 → key_up=0
// 第2次调用
static u8 key_up=1; // 不执行(跳过)
if(0) key_up=1; // 不执行
key_up 仍是 0 → 不检测新按键
模拟执行(mode=1)
// 第1次调用
static u8 key_up=1; // 执行 → key_up=1
if(1) key_up=1; // 执行 → key_up=1
...按下键 → key_up=0
// 第2次调用
static u8 key_up=1; // 不执行
if(1) key_up=1; // 执行 → key_up=1 ← 强行解锁!
→ 又可以检测了!
经典误区总结
| 误区 | 正确理解 |
|---|---|
“static u8 key_up=1; 每次都设1” |
错!只在第一次设1 |
“static 变量每次调用都重置” |
错!它保持值,除非你手动改 |
“那 key_up 怎么变0?” |
被 key_up=0; 改了,static 只是“记住”它 |
终极结论(一句话)
static u8 key_up=1;只初始化一次,mode=1靠if(mode) key_up=1;每次手动重置,才实现连按!
记忆口诀
static 初始化, 只执行一次头;
mode 为1时, 每次都解锁。
现在明白了吗?static 是“记忆”,mode=1 是“强制失忆”。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)