代码:

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))
  • 条件成立才进入检测:
    1. key_up == 1(之前已松开)
    2. 任意一个按键被按下(注意电平定义):
      • KEY0==0KEY1==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;
  • 优先级检测
    1. 先查 KEY0
    2. 再查 KEY1
    3. 最后查 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=1if(mode) key_up=1; 每次手动重置,才实现连按!


记忆口诀

static 初始化, 只执行一次头;
mode 为1时, 每次都解锁。

现在明白了吗?
static 是“记忆”,mode=1 是“强制失忆”。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐