找回密码
 立即注册
查看: 155|回复: 9

全功能单按键识别(0~254连击&非阻塞长按)

[复制链接]
  • 打卡等级:初来乍到
  • 打卡总天数:3
  • 最近打卡:2026-02-23 12:49:36
已绑定手机

1

主题

4

回帖

41

积分

新手上路

积分
41
发表于 2026-2-20 22:40:49 | 显示全部楼层 |阅读模式

非阻塞全功能单按键状态查询(1~254连击及长按识别)

毕业设计中按键检测程序改了很久,分享给大家。支持单击、双击、三连击、四连击、五连击、六连击、七连击、八连击、九连击、十连击、十一连击、十二连击、十三连击、十四击、十五击、十六连击……254连击以及无阻塞长按识别。程序与流程图略有不同,如有BUG或者更好建议,欢迎指正!

一、按键查询程序流程图

流程图110416.png

1.流程说明:

  1. 开始:根据传入的按键索引 keyn读取对应引脚电平。
  2. 释放处理:若引脚为高(释放),则增加释放消抖T1计数;连续10次检测到释放后,确认释放,重置相关变量并启动连击窗口T3计时。
  3. 按下处理:若引脚为低(按下),则增加长按T2计时、清除释放消抖T1计数;若之前为释放状态,则更新状态state、连击次数N1加1并重置连击窗口T3计时。
  4. 长按判断:若长按T2计时达到360ms,则将连击次数N1设为255(长按标志),置位长按标志B_L;否则清除长按标志B_L,并检查连击窗口是否结束(90ms),若结束则输出当前连击次数N1到N2( key_mode[])并清空连击计数N1。
  5. 键值快速清零:若N2(key_mode[])非零,则启动快速清零计时,超过1ms后将N2(key_mode)清零,确保输出仅维持短暂时间。

二、视频演示

**1.视频说明:**数码管高四位显示按键P4^7连击数,数码管低四位显示按键P3^3连击数; 长按流水灯循环,长按释放后数码管显示255,开机时数码管显示12345678。

三、按键状态查询代码

1.h文件

#ifndef __KEY_QUERY_H__
#define __KEY_QUERY_H__
#include <STC8051U.H>
#include <AI_USB.H>

#define KEY_Number 2 //物理按键数量

//按键引脚定义
sbit Key0 = P4^7;
sbit Key1 = P3^3;
//sbit Key2 = P3^6;

void KeyRead(u8 keyn);//查询按键状态(放入1ms中断中)

#endif

2.c文件

#include "key_query.h"

// 输出变量(供外部调用)
u16 xdata key_hold_timer[KEY_Number] = {0};	// 长按计时
u8  xdata key_nobl_long[KEY_Number] = {0};	// 非阻塞长按标志
u8  xdata key_mode[KEY_Number] = {0};		// 最终按键类型

// 按键扫描函数(需放入1ms中断中)
void KeyRead(u8 keyn)
{
	// 内部状态(静态局部,仅在此函数内使用)
	static u8 key_state[KEY_Number] = {0};          // 当前按下状态
	static u8 key_combinations[KEY_Number] = {0};   // 实时连击次数
	static u8 key_debounce_timer[KEY_Number] = {0}; // 释放消抖计时
	static u8 key_response_delay[KEY_Number] = {0}; // 连击窗口计时
	static u8 key_res_timer[KEY_Number] = {0};      // 键值快速清零计时
	bit  key_pin;  					// 当前按键引脚电平

    // 根据索引读取对应引脚
    switch (keyn)
    {
        case 0: key_pin = Key0; break;
        case 1: key_pin = Key1; break;
//        case 2: key_pin = Key2; break;
        default:{key_pin = 1;}	break; // 无效索引,视为高电平
    }

    // ---------- 按键按下/释放处理 ----------
    if (key_pin == 0)  // 按下(低电平有效)
    {
        key_hold_timer[keyn]++;          // 长按计时递增
        key_debounce_timer[keyn] = 0;    // 清除释放消抖计时

        if (key_state[keyn] == 0)        // 之前是释放状态,表示一次新的按下
        {
            key_state[keyn] = 1;
            key_combinations[keyn]++;     // 连击次数加1
            if (key_combinations[keyn] == 255) key_combinations[keyn] = 0; // 防止长按释放后误判为单击
            key_response_delay[keyn] = 0; // 清除连击窗口计时
        }
    }
    else  // 释放(高电平)
    {
        // 释放消抖:连续10次检测到高电平才确认为释放
        if (++key_debounce_timer[keyn] >= 10)
        {
            key_state[keyn] = 0;
            key_debounce_timer[keyn] = 0;
            key_hold_timer[keyn] = 0;      // 清除长按计时
            key_response_delay[keyn]++;    // 启动连击窗口计时
        }
    }

    // ---------- 长按判断及连击输出 ----------
    if (key_hold_timer[keyn] >= 360)       // 长按阈值360ms
    {
        key_combinations[keyn] = 255;      // 长按标记
        key_nobl_long[keyn] = 1;            // 置位长按标志
    }
    else
    {
        key_nobl_long[keyn] = 0;            // 可注释该行,在按键处理中手动清除长按标志

        // 连击窗口结束(90ms内无新按下),输出当前连击次数
        if (key_response_delay[keyn] >= 9)
        {
            key_response_delay[keyn] = 0;
            key_mode[keyn] = key_combinations[keyn];
            key_combinations[keyn] = 0;     // 清空连击计数
        }
    }

    // ---------- 键值快速清零(使key_mode仅保持约1ms)----------
    if (key_mode[keyn] != 0)
    {
        if (++key_res_timer[keyn] > 1)
        {
            key_res_timer[keyn] = 0;
            key_mode[keyn] = 0;
        }
    }
}

四、应用范例

1.键值查询处理代码

按键处理程序按实际需求移植,请忽略已注释代码。以下为视频演示中按键处理的代码。

(1).h

#ifndef __KEY_MANAGE_H__
#define __KEY_MANAGE_H__
#include <STC8051U.H>
#include <AI_USB.H>
#include <intrins.h>

void Key_Manage(void);  //键值处理程序

#endif

(2).C

#include "key_manage.h"

extern u8  xdata key_mode[];        //键值
extern u16 xdata key_hold_timer[];	//按键长按计时
extern u8  xdata key_nobl_long[];	//非阻塞按键长按标志位(用户手动消除)
extern u8  xdata Led_buf;			//LED流水灯缓存
extern u16 xdata display_buf1, display_buf2; //数码管显示数据十进制缓存

void Key_Manage(void)
{
	//系统按键操作
	if(key_mode[0] != 0) display_buf1 = key_mode[0], Display_Led_0123(); //刷新显示按键1连击数
	if(key_mode[1] != 0) display_buf2 = key_mode[1], Display_Led_4567(); //刷新显示按键2连击数

	//按键1处理
	if(key_mode[0] == 1)		//单击
	{
		Led_buf |= 1;			//LED0亮灯
	}
	else if(key_mode[0] == 2)	//双击
	{
		Led_buf |= 2;			//LED1亮灯
	}
	else if(key_mode[0] == 3)	//三连击
	{
		Led_buf |= 4;			//LED2亮灯
	}
	else if(key_mode[0] == 4)	//四连击
	{                   
		Led_buf |= 8;			//LED3亮灯  
	}                   
	else if(key_mode[0] == 5)	//五连击
	{                   
		Led_buf |= 16;			//LED4亮灯   
	}                   
	else if(key_mode[0] == 6)	//六连击
	{                   
		Led_buf |= 32;			//LED5亮灯 
	}                   
	else if(key_mode[0] == 7)	//七连击
	{
		Led_buf |= 64;			//LED6亮灯 
	}
	else if(key_mode[0] == 8)	//八连击
	{
		Led_buf |= 128;			//LED7亮灯
	}
	else if(key_nobl_long[0] == 1 && key_hold_timer[0]%160 == 0)//长按连加连减效果
	{
		Led_buf = _crol_(Led_buf, 1);//LED流水灯循环左移
	}

	//按键2处理
	if(key_mode[1] == 1)		//单击
	{                  
		Led_buf &= ~1;          //LED0熄灭
	}                  
	else if(key_mode[1] == 2)   //双击
	{                 
		Led_buf &= ~2;           //LED1熄灭
	}                  
	else if(key_mode[1] == 3)   //三连击
	{                  
		Led_buf &= ~4;          //LED2熄灭
	}                  
	else if(key_mode[1] == 4)   //四连击
	{                    
		Led_buf &= ~8;          //LED3熄灭
	}                    
	else if(key_mode[1] == 5)   //五连击
	{                    
		Led_buf &= ~16;         //LED4熄灭
	}                    
	else if(key_mode[1] == 6)   //六连击
	{                    
		Led_buf &= ~32;         //LED5熄灭
	}                    
	else if(key_mode[1] == 7)   //七连击
	{                  
		Led_buf &= ~64;         //LED6熄灭
	}                  
	else if(key_mode[1] == 8)   //八连击
	{                  
		Led_buf &= ~128;		//LED7熄灭
	}
	else if(key_nobl_long[1] == 1 && key_hold_timer[1]%160 == 0)//长按连加连减效果
	{
		Led_buf = _cror_(Led_buf, 1);//LED流水灯循环右移
	}

//	//按键2处理
//	if(key_mode[2] == 1)
//	{
//		Led_buf = 0;			//LDE 全部熄灭
//	}

	P0 = ~Led_buf;				//刷新流水灯
}

五、工程文件(按键控制流水灯视频)

AI8051U试验箱 V1.2 USB下载可用(CPU指令模式32位,IRC=40MHz)

upload 附件:多功能单按键流水灯加数码管演示.rar

1 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
回复

使用道具 举报 送花

  • 打卡等级:以坛为家III
  • 打卡总天数:823
  • 最近打卡:2026-03-05 08:20:20
已绑定手机

58

主题

2004

回帖

3568

积分

论坛元老

积分
3568
发表于 2026-2-21 08:39:09 | 显示全部楼层
谢谢分享
回复

使用道具 举报 送花

  • 打卡等级:以坛为家III
  • 打卡总天数:759
  • 最近打卡:2026-03-05 09:05:43
已绑定手机

16

主题

1402

回帖

4889

积分

论坛元老

积分
4889
发表于 2026-2-21 09:43:35 | 显示全部楼层
能否优化成用一个定时器实现呢?
回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:3
  • 最近打卡:2026-02-23 12:49:36
已绑定手机

1

主题

4

回帖

41

积分

新手上路

积分
41
发表于 2026-2-21 10:13:42 来自手机 | 显示全部楼层
21cnsound 发表于 2026-2-21 09:43
能否优化成用一个定时器实现呢?

单片机资源只使用了一个定时器1ms中断。流程图中T1,T2,T3等是抽象出来的中间变量。

点评

没仔细看代码,这样就太好了。  发表于 2026-2-22 07:40
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:415
  • 最近打卡:2026-03-05 09:04:51

12

主题

534

回帖

1242

积分

金牌会员

积分
1242
发表于 2026-2-21 12:18:20 | 显示全部楼层
这个非常实用,谢谢分享!
回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:3
  • 最近打卡:2026-02-23 12:49:36
已绑定手机

1

主题

4

回帖

41

积分

新手上路

积分
41
发表于 2026-2-21 23:16:08 来自手机 | 显示全部楼层
代码冗余,明天发精简过的版本和流程详解
回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:3
  • 最近打卡:2026-02-23 12:49:36
已绑定手机

1

主题

4

回帖

41

积分

新手上路

积分
41
发表于 2026-2-21 23:41:44 来自手机 | 显示全部楼层
代码冗余,我改一下再发精简版的代码。
回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:3
  • 最近打卡:2026-02-23 12:49:36
已绑定手机

1

主题

4

回帖

41

积分

新手上路

积分
41
发表于 2026-2-23 12:49:36 | 显示全部楼层
[quote][size=2][url=forum.php?mod=redirect&goto=findpost&pid=209597&ptid=22715][color=#999999]不约而同 发表于 2026-2-21 23:16[/color][/url][/size] 代码冗余,明天发精简过的版本和流程详解[/quote]

主要简化点:

  1. 移除重复代码:原 switch中三个 case的重复逻辑被合并为统一处理,通过读取引脚数组(实际用 switch映射)实现。
  2. 隐藏内部状态:将 key_statekey_combinationskey_debounce_timerkey_response_delaykey_res_timer改为函数内部的静态变量,不再占用全局命名空间。
  3. 移除多余标志位:移除按键单次响应标志位 bit B_push11,B_push12,B_push21,B_push22,B_push23,B_push31 ,B_push32,B_push33
  4. 保持功能不变:所有按键识别逻辑(连击计数、长按判断、非阻塞标志、输出定时)均与原代码一致。

使用时,只需在1ms定时器中断中循环调用 KeyRead(0)KeyRead(1)KeyRead(2)即可。输出变量 key_modekey_nobl_longkey_hold_timer供外部查询。

回复

使用道具 举报 送花

  • 打卡等级:以坛为家III
  • 打卡总天数:631
  • 最近打卡:2026-03-04 08:45:22

33

主题

2882

回帖

6465

积分

论坛元老

积分
6465
发表于 2026-2-24 11:11:27 | 显示全部楼层
要是能再识别一下按下事件,弹起事件。组合按键这种,,那就是起飞~
参考例程并不是对技术参 考手册的补充,而是对技术参 考手册的解释。
技术参 考手册不应该需要参考例程作为补充,而是解释成了参考例程的样子
回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-3-6 02:12 , Processed in 0.132236 second(s), 91 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表