矩阵键盘的按下、长按、松手检测【以51单片机为例】

2024-01-21 23:10

本文主要是介绍矩阵键盘的按下、长按、松手检测【以51单片机为例】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

矩阵键盘的按下、长按、松手检测

  • 51单片机矩阵键盘电路原理图
    • 矩阵键盘的原理
  • 处理扫描得到的数据
    • 按键的结构体
    • 初始化
    • 处理过程(函数)
      • 三个状态判别的基本条件
      • 处理过程
      • 键值功能函数的处理
      • 三种状态的事件处理
    • 扫描函数
  • 主循环中的处理和执行、定时器中断的扫描处理
    • 定时器处理
    • 主循环处理
  • 功能简介
  • 总结

51单片机矩阵键盘电路原理图

在这里插入图片描述
上图为51单片机矩阵的连接图,为4*4的矩阵键盘,只需要8个引脚就可以控制16个按键。

矩阵键盘的原理

在这里简单说明一下吧,例如我的矩阵键盘是接在了P1口,内部有上拉电阻,所以在上电的情况下,默认输出的是高电平。

扫描第一行:把P17引脚拉低,P16、P15、P14三行全部拉高,四列也就是P13、P12、P11、P10四个引脚全部设置为高电平。
检测按下状态:当我们按下第一行某个按键,P17引脚会是低电平,对应的列引脚也会被拉低,我们回读P1引脚,如果发现扫描的值与回读的值不一样,就确定第一行某个按键被按下,回读的值低四位哪一位为0就确定是哪一列被按下。
以下为检测键盘的伪代码,正式代码我会放在最后。

unsigned char keyValue;
P1 = 0x7f;//P17引脚拉低,其他七个引脚全高
if((P1&0x7f) != 0x7f)
{	//回读P1的值发现与给定的不一样,有按键被按下keyValue = P1;
}
//得到keyValue的值

行数有四行,我们放到for循环中依次执行,同时给一个四个长度数组保存下来每一行的P1值

uint8_t key_row[4] = {0x7f, 0xbf, 0xdf, 0xef};
uint8_t keyValue = 0x00;
uint8_t i=0;
for(i=0;i<4;i++)
{P1 = key_row[i];if(P1 & key_row[i] != key_row[i]){keyValue = key_row[i];//得到键值break;}
}
return keyValue;//如果在函数里,可以直接加上返回退出扫描函数了

好了,51的矩阵键盘原理就先说到这里,重点是扫描到的数据如何进行处理,判别是长按和松手

处理扫描得到的数据

这里用到了一种方法,处理器扫描速度很快,在我们按下的一瞬间扫描函数不知道记录多少次相同的返回结果了,所以我们根据这个处理速度来判别我们的键盘是哪种状态,在我们的扫描函数放在中断里去获取键值,键值的处理和键值对应的事件我们全放在主循环中执行。
我们开发单片机程序最好将事件多的处理交给主循环。

按键的结构体

typedef enum
{Key_Short_Press,//短按Key_Lift,//抬起Key_Long_Press,//长按Key_Compound_Press,//复合按键Key_None,//none
}key_state_e;typedef struct keyBoard
{uint8_t key_value[KEY_VALUE_MAX];//键值uint8_t key_Number;//按键数量bool key_Action_Flag;//执行标志void (*key_func)(struct keyBoard *);//执行函数uint16_t key_Last_value;//上次键值key_state_e key_status;//当前状态uint16_t Key_Long_Count;//长按扫描计数bool key_long_press_action_flag;//长按操作的执行标志uint16_t key_long_scan_Count;//长按
}Key_Info_t;

初始化

void ScanKeyInit(Key_Info_t *Key)
{Key->key_Number = 0;Key->key_Action_Flag = false;Key->key_func = keyFuncProcess;//为键值事件的处理函数Key->key_Last_value = 0x00;Key->key_value[0] = 0x00;Key->key_value[1] = 0x00;Key->key_status = Key_None;Key->Key_Long_Count = 0;Key->key_long_scan_Count = 0;
}

处理过程(函数)

先创建一个按键的结构体Key_Info_t Key_Board;

三个状态判别的基本条件

1.判断当前是不是按下状态

bool KeyIsDown(uint8_t keyvalue)
{if (keyvalue != 0x00){return true;}else{return false;}
}

2.判断当前是不是刚刚被按下

bool KeyIsClick(uint8_t keyvalue)
{if (KeyIsDown(keyvalue)){if (Key_Board.key_value[0] == 0X00) /*不能判断last值,必须判断收录的值*/return true;elsereturn false;}elsereturn false;
}

3.判断当前是不是刚刚被抬起

bool KeyIsUp(uint8_t keyvalue)
{if (KeyIsDown(keyvalue))return false;else{if (Key_Board.key_Last_value != 0x00)return true;elsereturn false;}
}

处理过程

void KeyValueProcess(uint8_t key, Key_Info_t *keyboard)
{if (key != 0x00){if (KeyIsClick(key)){/* 刚按下的状态 */keyboard->key_value[keyboard->key_Number++] = key;keyboard->key_Last_value = 0x00;keyboard->key_Action_Flag = true;keyboard->key_status = Key_Short_Press;keyboard->Key_Long_Count = systicks;}else if (KeyIsDown(key) && keyboard->key_value[0] == key && keyboard->key_Number == 1){/* 处理长按 */if (systicks - keyboard->Key_Long_Count > 1000) /* 2s */{keyboard->key_Last_value = keyboard->key_value[0];keyboard->key_status = Key_Long_Press;keyboard->key_Action_Flag = true;}else{keyboard->key_Last_value = keyboard->key_value[0];keyboard->key_Action_Flag = false;}}else if (KeyIsDown(key) && keyboard->key_value[0] != key && keyboard->key_Number == 1){/* 复合按键 */keyboard->key_Last_value = keyboard->key_value[0];keyboard->key_value[1] = key;keyboard->key_Action_Flag = true;keyboard->key_status = Key_Compound_Press;keyboard->key_Number = 2;}/* 按下状态 + 按键与之前两个按键某一个相同 + 按键记录数已经到2 */else if(KeyIsDown(key)  && keyboard->key_Number == 2 && (key==keyboard->key_value[0] || key==keyboard->key_value[1])){if (keyComNumber>10) /* 长时间检测到只有一个按键,确保另一个按键已被抬起,先处理抬起的按键 */{if(key == keyboard->key_value[0])	/* 第一个按键没松手 */{keyboard->key_value[1] = 0x00;keyboard->key_Number = 1;keyboard->key_status = Key_Lift;keyboard->key_Action_Flag = true;}else if(key == keyboard->key_value[1])	/* 第二个按键没松手 */{keyboard->key_value[0] = 0x00;keyboard->key_Number = 1;keyboard->key_status = Key_Lift;keyboard->key_Action_Flag = true;}else{return;}}}}else if (KeyIsUp(key)){/* 按键抬起 */keyboard->key_Last_value = key;keyboard->key_status = Key_Lift;keyboard->key_value[keyboard->key_Number--] = 0x00;keyboard->key_Action_Flag = true;}else{/* none */keyboard->key_Action_Flag = false;keyboard->key_value[0] = 0x00;keyboard->key_value[1] = 0x00;keyboard->key_status = Key_None;keyboard->key_Last_value = 0x00;}
}

以上为处理获得键值的过程,唯一bug就是复合按键按下之后,必须两个按键都抬起才能全部释放,不然抬起一个去按另一个无法处理。这个bug留着以后我开发真要碰到的话再解决。

键值功能函数的处理

void keyFuncProcess(Key_Info_t *key)
{if (key->key_Action_Flag == false)return;if (key->key_status == Key_Short_Press){KeyShortAction(key);}else if (key->key_status == Key_Long_Press){if ((systicks - key->key_long_scan_Count) > 1000){key->key_long_press_action_flag = true;}if (key->key_long_press_action_flag == true) /* 长按不松手,每2s执行一次程序 */{KeyLongAction(key);key->key_long_press_action_flag = false;key->key_long_scan_Count = systicks;}}else if (key->key_status == Key_Lift){KeyLiftAction(key);}else if (key->key_status == Key_Compound_Press){KeyCompoundAction(key);}
}

三种状态的事件处理

1.检测到按下

void KeyShortAction(Key_Info_t *key)
{if (key->key_status != Key_Short_Press)return;switch (key->key_value[0]){case S1:sendString("S1 is down\r\n");break;case S2:sendString("S2 is down\r\n");break;case S3:sendString("S3 is down\r\n");break;case S4:sendString("S4 is down\r\n");break;case S5:sendString("S5 is down\r\n");break;case S6:sendString("S6 is down\r\n");break;case S7:sendString("S7 is down\r\n");break;case S8:sendString("S8 is down\r\n");break;case S9:sendString("S9 is down\r\n");break;case S10:sendString("S10 is down\r\n");break;case S11:sendString("S11 is down\r\n");break;case S12:sendString("S12 is down\r\n");break;case S13:sendString("S13 is down\r\n");break;case S14:sendString("S14 is down\r\n");break;case S15:sendString("S15 is down\r\n");break;case S16:sendString("S16 is down\r\n");break;}key->key_status = Key_None;
}

2.检测是长按

void KeyLongAction(Key_Info_t *key)
{if (key->key_status != Key_Long_Press)return;switch (key->key_value[0]){case S1:sendString("S1 is LongPress\r\n");break;case S2:sendString("S2 is LongPress\r\n");break;case S3:sendString("S3 is LongPress\r\n");break;case S4:sendString("S4 is LongPress\r\n");break;case S5:sendString("S5 is LongPress\r\n");break;case S6:sendString("S6 is LongPress\r\n");break;case S7:sendString("S7 is LongPress\r\n");break;case S8:sendString("S8 is LongPress\r\n");break;case S9:sendString("S9 is LongPress\r\n");break;case S10:sendString("S10 is LongPress\r\n");break;case S11:sendString("S11 is LongPress\r\n");break;case S12:sendString("S12 is LongPress\r\n");break;case S13:sendString("S13 is LongPress\r\n");break;case S14:sendString("S14 is LongPress\r\n");break;case S15:sendString("S15 is LongPress\r\n");break;case S16:sendString("S16 is LongPress\r\n");break;}key->key_status = Key_None;
}

3.检测按键被抬起

void KeyLiftAction(Key_Info_t *key)
{uint8_t keyvalue = 0x00;if (key->key_status != Key_Lift)return;if (key->key_value[1] == 0x00 && key->key_value[0] != 0x00)keyvalue = key->key_value[0];else if (key->key_value[1] != 0x00)keyvalue = key->key_value[1];switch (keyvalue){case S1:sendString("S1 is Lift\r\n");break;case S2:sendString("S2 is Lift\r\n");break;case S3:sendString("S3 is Lift\r\n");break;case S4:sendString("S4 is Lift\r\n");break;case S5:sendString("S5 is Lift\r\n");break;case S6:sendString("S6 is Lift\r\n");break;case S7:sendString("S7 is Lift\r\n");break;case S8:sendString("S8 is Lift\r\n");break;case S9:sendString("S9 is Lift\r\n");break;case S10:sendString("S10 is Lift\r\n");break;case S11:sendString("S11 is Lift\r\n");break;case S12:sendString("S12 is Lift\r\n");break;case S13:sendString("S13 is Lift\r\n");break;case S14:sendString("S14 is Lift\r\n");break;case S15:sendString("S15 is Lift\r\n");break;case S16:sendString("S16 is Lift\r\n");break;}key->key_status = Key_None;
}

4.复合按键(有需要的可以用)

void KeyCompoundAction(Key_Info_t *key)
{if (key->key_status != Key_Compound_Press)return;if ((key->key_value[0] == S1 && key->key_value[1] == S6) || (key->key_value[1] == S1 && key->key_value[0] == S6)){sendString("S1 And S2 Compound Press\r\n");}key->key_status = Key_None;
}

复合按键这里只处理了S1 和 S2 一种,毕竟16个按键不可能全部都有匹配的开发。

扫描函数

这里我把扫描函数分为两个正反轮询扫描,目的是检测多按键的情况,如果有需要的可以两个都使用,如果只是单按键,那两个中选一个就可以

uint8_t getKeyValue(void)
{uint8_t i = 0;uint8_t keyValue = 0x00;for (i = 0; i < 4; i++){P1 = key_row[i];if ((P1 & key_row[i]) != key_row[i]){keyValue = P1;goto k1;}}
k1:return keyValue;
}uint8_t getKeyValue2(void)
{char i = 0;uint8_t keyValue = 0x00;for (i = 3; i > -1; i--){P1 = key_row[i];if ((P1 & key_row[i]) != key_row[i]){keyValue = P1;goto k2;}}
k2:return keyValue;
}

主循环中的处理和执行、定时器中断的扫描处理

定时器我是用的TIMER0,时间为1ms执行一次,正反扫描如果你只需要一个按键检测和处理,去掉其中的if(!isLeftModeFlag)保留一个执行就可以

定时器处理

void interrupt_timer1(void) interrupt 1
{TH0 = (65536-1000)/256;    // 中断后,赋初值;TL0 = (65536-1000)%256; if(!isLeftModeFlag){keyvalue = getKeyValue();	//正扫描}else{keyvalue = getKeyValue2(); //反扫描}if(keyvalue != 0x00)//上次有按键就切换扫描顺序isLeftModeFlag = ~isLeftModeFlag;if(lastKeyValue==keyvalue){keyComNumber++; 	//长按的}else{keyComNumber = 0;}sysCount++;       // 每次中断,计数 累加 1;systicks++;		//tick计数,1ms一次,用来判断长按的检测很不错if(sysCount>=1000){runAppLamp();     //运行灯sysCount = 0; }
}

主循环处理

	ScanKeyInit(&Key_Board);while (1){KeyValueProcess(keyvalue,&Key_Board);Key_Board.key_func(&Key_Board);}

我没有加定时器和串口的初始化,这个各位使用的时候加上就可以。

功能简介

能够检测矩阵键盘的按下、长按、松手三种状态,同时添加了多按键,但是只能检测多按键按下,没有抬起和松手的处理。
长按2s执行长按事件,不松手的话每隔2s执行一次事件直到松手。

总结

可以看到除了主循环其他地方全是if进行判别的,可以避免某个地方出错卡死导致整个产品不能启动,这种方式还是挺不错。
在这里只是简单表明了我开发的一个逻辑思路,完整代码可以去我的git工程查看完整的51单片机矩阵键盘的项目。
zilboe/MatrixScanKey.git

这篇关于矩阵键盘的按下、长按、松手检测【以51单片机为例】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/631112

相关文章

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

day-51 合并零之间的节点

思路 直接遍历链表即可,遇到val=0跳过,val非零则加在一起,最后返回即可 解题过程 返回链表可以有头结点,方便插入,返回head.next Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}*

hdu 4565 推倒公式+矩阵快速幂

题意 求下式的值: Sn=⌈ (a+b√)n⌉%m S_n = \lceil\ (a + \sqrt{b}) ^ n \rceil\% m 其中: 0<a,m<215 0< a, m < 2^{15} 0<b,n<231 0 < b, n < 2^{31} (a−1)2<b<a2 (a-1)^2< b < a^2 解析 令: An=(a+b√)n A_n = (a +

基于 YOLOv5 的积水检测系统:打造高效智能的智慧城市应用

在城市发展中,积水问题日益严重,特别是在大雨过后,积水往往会影响交通甚至威胁人们的安全。通过现代计算机视觉技术,我们能够智能化地检测和识别积水区域,减少潜在危险。本文将介绍如何使用 YOLOv5 和 PyQt5 搭建一个积水检测系统,结合深度学习和直观的图形界面,为用户提供高效的解决方案。 源码地址: PyQt5+YoloV5 实现积水检测系统 预览: 项目背景

JavaFX应用更新检测功能(在线自动更新方案)

JavaFX开发的桌面应用属于C端,一般来说需要版本检测和自动更新功能,这里记录一下一种版本检测和自动更新的方法。 1. 整体方案 JavaFX.应用版本检测、自动更新主要涉及一下步骤: 读取本地应用版本拉取远程版本并比较两个版本如果需要升级,那么拉取更新历史弹出升级控制窗口用户选择升级时,拉取升级包解压,重启应用用户选择忽略时,本地版本标志为忽略版本用户选择取消时,隐藏升级控制窗口 2.

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

键盘快捷键:提高工作效率与电脑操作的利器

键盘快捷键:提高工作效率与电脑操作的利器 在数字化时代,键盘快捷键成为了提高工作效率和优化电脑操作的重要工具。无论是日常办公、图像编辑、编程开发,还是游戏娱乐,掌握键盘快捷键都能带来极大的便利。本文将详细介绍键盘快捷键的概念、重要性、以及在不同应用场景中的具体应用。 什么是键盘快捷键? 键盘快捷键,也称为热键或快捷键,是指通过按下键盘上的一组键来完成特定命令或操作的方式。这些快捷键通常涉及同

hdu 6198 dfs枚举找规律+矩阵乘法

number number number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Problem Description We define a sequence  F : ⋅   F0=0,F1=1 ; ⋅   Fn=Fn

单片机毕业设计基于单片机的智能门禁系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍程序代码部分参考 设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订