单片机——按键扫描

2023-05-16

按键扫描,我想应该是比较简单的单片机应用了,但是有时候看起来简单的东西反而不好写。

本文拿大部分人觉得简单的按键扫描聊聊我工作至今对于软件结构的理解。嗯,对的,是结构,不是架构,暂时不敢提架构这个词。

按键扫描,我当时入门的时候是看的郭天祥的51单片机入门的,视频里面讲的是循环扫描io引脚,一旦有电平变化就利用软件延时消抖,模拟延时就是让单片机空转,什么也不做,等待个几十毫秒之后再检测一次如果电平没有变化就认为按键按下。这种方法也能实现按键检测,好处是简单,缺点是占用太多的软件资源,CPU空转这一点我觉得挺不好的。

下面说说我个人对于一个按键检测的代码理解。

按键检测需要做什么事情呢?一个是按键按下的这个物理事件的检测,一个是按下时候的消抖。能想到这两个已经可以写一段代码来实现功能了。

 

#define DEBOUNCE 10//延时消抖时间
uint8 key_scan( uint8 keycur )
{
	static uint8 KeyLast = KEY_NULL; 	
	static uint8 KeyCountdowm = 0; 	
	uint8 keyret = KEY_NULL;   	    

	if(  keycur != KEY_NULL )	//如果检测到按键按下,开始进行延时消抖			
	{
		if( KeyLast == keycur  || KEY_NULL == KeyLast)				
		{
			KeyCountdowm++;				
		}
	}
	else
	{
		if( KeyCountdowm >= DEBOUNCE)//按键抬起
		{
			keyret = KeyLast;
		}
		else
		{
		        keyret = KEY_NULL;
		}

		KeyCountdowm = 0;
	}
	
	KeyLast = keycur;//按键备份值更新

	return keyret;
}

上面是一个简单的按键扫描函数,函数需要放置在一个10ms的定时函数里面,注意是定时函数不是定时中断函数,需要传递按键信息,这个信息可以是从通信函数获取的,也可以是直接读取IO端口获得,返回一个消抖过后的键值,键值不做逻辑判断。

 

然后我们来聊聊这个消抖函数,这个消抖函数只实现了一个功能,按下消抖,那如果使用环境或者硬件设计缺陷,导致抬起的时候也有抖动呢?所以需要添加抬起消抖。

下面对函数进行优化一下

 

#define DEBOUNCE 10//延时消抖时间
uint8 key_scan( uint8 keycur )
{
	static uint8 KeyLast = 0; 	
	static uint8 KeyCountdowm = 0; 	
	static uint8 KeyCountup = 0; 	
	unsigned uint8 keyret = 0;   	  

	keyret = KEY_NULL;
	if(  keycur != KEY_NULL )				
	{
		if( KeyLast == keycur || KEY_NULL == KeyLast)			
		{
			KeyCountdowm++;			
			KeyCountup = 0;
		}
	}
	else
	{
		KeyCountup++;	

		if( KeyCountup > 1 ) 
		{
			KeyCountup = 0;	

			if( KeyCountdowm >= DEBOUNCE )
			{
				keyret = KeyLast;
				KeyCountdowm = 0;
			}
		}
	}
	
	KeyLast = keycur;	
	return keyret;
}

添加了20ms的抬起消抖,这段代码添加了两个消抖检测,一个是在抬起的时候有20ms的消抖,一个是在按键按下过程的一个过程消抖。

上面的代码实现的是抬起有效,那么如果需要做到按下有效呢?

#define DEBOUNCE 10//延时消抖时间
uint8 key_scan( uint8 keycur )
{
	static uint8 KeyLast = 0; 	
	static uint8 KeyCountdowm = 0; 	
	static uint8 KeyCountup = 0; 	
	unsigned uint8 keyret = 0;   	  

	keyret = KEY_NULL;
	if(  keycur != KEY_NULL )				
	{
		if( KeyLast == keycur || KEY_NULL == KeyLast)			
		{
			KeyCountdowm++;			
			KeyCountup = 0;
			
			if( KeyCountdowm >= DEBOUNCE )
			{
				keyret = KeyLast;
			}
		}
	}
	else
	{
		KeyCountup++;	

		if( KeyCountup > 1 ) 
		{
			KeyCountup = 0;	
			KeyCountdowm = 0;
		}
	}
	
	KeyLast = keycur;	
	return keyret;
}

挪动按键的延时检测判断语句,在按下超过延时消抖时间的时候,返回按键按下值。

 

然后一个简单按键检测函数就实现了,下面再给这个函数添加长按键判断和连续按键。

#define DEBOUNCE 10//延时消抖时间
#define LONGPRESS 100//长按键判断函数
uint16 key_scan( uint8 keycur )
{
	static uint8 KeyLast = 0; 	
	static uint8 KeyCountdowm = 0; 	
	static uint8 KeyCountup = 0; 	
	unsigned uint16 keyret = 0;   	  

	keyret = KEY_NULL;
	if(  keycur != KEY_NULL )				
	{
		if( KeyLast == keycur || KEY_NULL == KeyLast)			
		{
			KeyCountdowm++;			
			KeyCountup = 0;
			
			if( KeyCountdowm >= DEBOUNCE && KeyCountdowm < LONGPRESS)//短按键判断
			{
				keyret = KeyLast;
			}
			else if( KeyCountdowm == LONGPRESS )//长按键判断				
			{
				keyret = KeyLast;	
				keyret |= 0x0100;		
			}
			else if(KeyCountdowm > LONGPRESS+DEBOUNCE)//连续按键判断
			{
				KeyCountdowm -= DEBOUNCE;
				keyret = KeyLast;	
				keyret |= 0x0200;	
			}
		}
	}
	else
	{
		KeyCountup++;	

		if( KeyCountup > 1 ) 
		{
			KeyCountup = 0;	
			KeyCountdowm = 0;
		}
	}
	
	KeyLast = keycur;	
	return keyret;
}

 

当有多个设备的时候,可以将静态局部变量修改为结构体指针的形式,如下

type struct key
{
	uint8 Last;
	uint8 CountDowm;
	uint8 CountUp;
}KEY_TYPE;

#define DEBOUNCE 10//延时消抖时间
#define LONGPRESS 100//长按键判断函数

uint16 key_scan( KEY_TYPE *Key ,uint8 keycur)
{	
	unsigned uint16 keyret = 0;   	  

	keyret = KEY_NULL;
	if(  keycur != KEY_NULL )				
	{
		if( Key->Last == keycur || KEY_NULL == Key->Last)			
		{
			Key->CountDowm++;			
			Key->CountUp = 0;
			
			if( Key->CountDowm >= DEBOUNCE && Key->CountDowm < LONGPRESS)//短按键判断
			{
				keyret = KeyLast;
			}
			else if( Key->CountDowm == LONGPRESS )//长按键判断				
			{
				keyret = Key->KeyLast;	
				keyret |= 0x0100;		
			}
			else if(KeyCKey->CountDowmountdowm > LONGPRESS+DEBOUNCE)//连续按键判断
			{
				Key->CountDowm -= DEBOUNCE;
				keyret = Key->KeyLast;	
				keyret |= 0x0200;	
			}
		}
	}
	else
	{
		Key->CountUp++;	

		if( Key->CountUp > 1 ) 
		{
			Key->CountUp = 0;	
			Key->CountDowm = 0;
		}
	}
	
	Key->KeyLast = keycur;	
	return keyret;
}

 

最后说说这个功能的实现,按键检测分为三个部分,一个是按键获取函数,一个是消抖,一个是按键筛选函数,先把代码贴上来。

type struct key
{
	uint8 Last;
	uint8 CountDowm;
	uint8 CountUp;
}KEY_TYPE;

#define DEBOUNCE 10//延时消抖时间
#define LONGPRESS 100//长按键判断函数
/*
* description: 按键消抖函数
* intput:按键结构体,键值
* output:键值
* 
*/
uint16 key_scan( KEY_TYPE *Key ,uint8 keycur)
{
	unsigned uint16 keyret = 0;   	  

	keyret = KEY_NULL;
	if(  keycur != KEY_NULL )				
	{
		if( Key->Last == keycur || KEY_NULL == Key->Last)			
		{
			Key->CountDowm++;			
			Key->CountUp = 0;
			
			if( Key->CountDowm >= DEBOUNCE && Key->CountDowm < LONGPRESS)//短按键判断
			{
				keyret = KeyLast;
			}
			else if( Key->CountDowm == LONGPRESS )//长按键判断				
			{
				keyret = Key->KeyLast;	
				keyret |= 0x0100;		
			}
			else if(KeyCKey->CountDowmountdowm > LONGPRESS+DEBOUNCE)//连续按键判断
			{
				Key->CountDowm -= DEBOUNCE;
				keyret = Key->KeyLast;	
				keyret |= 0x0200;	
			}
		}
	}
	else
	{
		Key->CountUp++;	

		if( Key->CountUp > 1 ) 
		{
			Key->CountUp = 0;	
			Key->CountDowm = 0;
		}
	}
	
	Key->KeyLast = keycur;	
	return keyret;
}

/*
* description: 获取按键的函数
* intput:none
* output:键值
* 
*/
uint8 GetKeyValue(void)
{
	uint8 ret=NULL;
	/*
	这个部分的代码需要自己实现,这里可以从通信函数如iic,spi获得键值,也可以从gpio端口获得键值
	*/
	return ret;
}


#define KEY_NULL 0
#define KEY_UP 1
#define KEY_DOWN 2
#define KEY_ON 3
#define KEY_UP_L 4
#define KEY_DOEN_L 5
#define KEY_ON_L 6
/*
* description: 筛选按键的函数,只筛选需要的键值
* intput:键值
* output:键值
* 
*/
uint8 GetKeyValue(uint8 key)
{
	uint8 ret=KEYNULL;
	switch(key)
	{
		0x01:
		{
			ret = KEY_UP;
		}
			break;
		0x0101:
		{
			ret = KEY_UP_L;
		}
			break;
		default:
			break;
	}
	
	return ret;
}

 

函数的三个部分的按键获取函数部分需要自己去编写,不同的键值来源不同,我试过从IIC中读取键值,也试过从GPIO中读取键值。

然后是消抖函数,消抖函数需要如果有多个按键来源的话需要定义多个结构体,如果只有一个键值来源可以替换成静态局部变量版的函数。

分成三个部分的好处是,函数间各干各事情,互不影响,更容易读,我在接手别人的代码时候,如果涉及到按键部分出现问题的话,一般先问上一个维护的人做没做过程消抖,如果回答说有做,好的,接着问怎么做的,一般把对方的过程消抖搞懂了,基本就懂了按键这部分的代码。如果对方说没有做或者很茫然的看着我,好吧,我会直接把对方的这部分代码删掉,重写。

 

写于2017年7月30日 一个没有空调的夏天

深圳

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

单片机——按键扫描 的相关文章

随机推荐

  • 如何使用cvx工具箱求解凸优化问题呢?

    以下是一个简单的MATLAB求解凸优化问题的案例 xff0c 使用cvx 工具箱求解 xff1a 1 导入cvx工具箱 addpath 39 cvx 39 2 定义问题 n 61 2 变量数量 x 61 sym 39 x 39 n 1 定义
  • 基于stm32单片机智能导盲拐杖源程序Proteus仿真设计

    功能介绍 xff1a 采用stm32单片机作为主控CPU xff0c 采用srf05超声波模块测量障碍物距离 xff0c LCD1602显示屏显示当前的障碍物距离 xff0c 通过传感器测量障碍物的距离的远近来进行相关报警 xff0c 距离
  • virtualbox上安装ubuntu20.04清华源镜像

    一 下载Ubuntu镜像文件 这里我选择国内清华源下载ubuntu镜像文件 步骤一 xff1a 清华源下载地址 xff1a Index of ubuntu releases 20 04 清华大学开源软件镜像站 Tsinghua Open S
  • 计算机视觉(一):初识OpenCV,更好更快掌握OpenCV

    计算机视觉 xff08 一 xff09 xff1a 初识OpenCV 近几个月 xff0c 也是一直在寻找自己以后的方向 xff0c 一直在迷茫中 xff0c 但也在不断得探索着 觉得自己最感兴趣的还是计算机技术这一块 xff0c 尤其是计
  • 虚拟机扩容(超详细实测好用步骤)

    第一步 xff1a 把虚拟机关机 第二步 xff1a 点击编辑虚拟机设置 xff0c 选择硬盘 xff0c 点击扩展 xff0c 如下图 xff1a 第三步 xff1a 输入你现在想要的磁盘大小 xff08 如果你原来是200G xff0c
  • 结构体的对齐规则(结构体的计算)

    1 第一个成员在与结构体变量偏移量为0的地址处 2 其他成员变量要对齐到某个数字 xff08 对齐数 xff09 的整数倍的地址处 对齐数 61 编译器默认的一个对齐数 与 该成员大小的较小值 3 结构体总大小为最大对齐数 xff08 每个
  • hostapd 配置项解析

    hostapd 配置项解析 1 interface2 wps state3 hw mode4 channel5 beacon int6 max num sta 1 interface 一般默认interface 61 wlan0 注 xff
  • 滑动窗口算法总结

    算法目的 滑动窗口法 xff0c 也叫尺取法 xff08 可能也不一定相等 xff0c 大概就是这样 61 61 xff09 xff0c 可以用来解决一些查找满足一定条件的连续区间的性质 xff08 长度等 xff09 的问题 由于区间连续
  • linux软链接的创建、删除和更新

    大家都知道 xff0c 有的时候 xff0c 我们为了省下空间 xff0c 都会使用链接的方式来进行引用操作 同样的 xff0c 在系统级别也有 在Windows系列中 xff0c 我们称其为快捷方式 xff0c 在Linux中我们称其为链
  • 进程、线程、多进程、多线程的优缺点和区别

    进程 xff1a 是并发执行的程序在执行过程中分配和管理资源的基本单位 xff0c 是一个动态概念 xff0c 竞争计算机系统资源的基本单位 线程 xff1a 是进程的一个执行单元 xff0c 是进程内科调度实体 比进程更小的独立运行的基本
  • Postman 汉化(Postman中文版)

    1 首先从官网下载postMan安装包 postman官网下载地址 Download Postman Get Started for Free 2 下载postMan 汉化包 app zip postman汉化包 Releases hlmd
  • UDP的客户端和服务器端的实例(VC6.0实现)

    服务器端程序 xff1a UdpServer cpp Defines the entry point for the console application include 34 stdafx h 34 include lt stdio h
  • STM32中关于串口中断的调试(不断进入发送中断的原因)

    说来惭愧 xff0c 前日在调试stm32f10系列的单片机的时候 xff0c 想做一个关于串口发送的状态机 xff0c 每隔100毫秒发送一次命令 没有用DMA xff0c 就是想单纯的使用发送中断来数据 xff0c 结果在调试的时候一直
  • Psoc Creator 入门——空工程的建立

    最近在做psoc 4000芯片的开发 xff0c 现在简单的说说怎么利用psoc creator进行开发 首先 xff0c 安装psoc creator xff0c 我使用的版本是4 0 安装过程省略 xff0c 赛普拉斯官网有下载链接 x
  • mbedtls使用openssl生成的自签名证书进行TLS实验

    目录 1 使用openssl生成自签名证书2 VS2013编译mbedtls3 mbdtls默认对证书的要求4 mbdtls测试例子详解5 运行测试程序 1 使用openssl生成自签名证书 openssl是一个安全套接字层密码库 xff0
  • IIC的通信波形分析

    关于IIC xff0c 不解释它的历史了 xff0c 有兴趣自己去百度看看 xff0c 本文的图片是由周立功的LAB6021逻辑分析仪抓取的 xff0c 通信的波形是抓取的cypress的psoc 4000芯片得到的 最近项目需要用到触摸I
  • linux下的CSV文件操作

    先介绍一下什么是csv文件 xff0c 这是一种简单的文本文件 xff0c 也被称为逗号分隔值文件 主要是用于存储简单的数据 xff0c 下面在weindows下用UE简单生成一下文件 然后用excel打开 这就是一个简单的csv文件 xf
  • GNU makefile入门——刚开坑,没有干货

    一个完整的makefile文件包含5个部分的内容 xff1a 显示规则 xff0c 隐含规则 xff0c 变量和指示符 xff0c 注释 显示规则 xff1a 包括目标 xff1a 依赖规则 命令 隐含规则 xff1a make根据目标文件
  • Renesas CS+ for ca cx入门(一)

    这是一篇关于Renesas的CS 43 for ca xff08 以下简称CA xff09 的入门简介 xff0c 在网上关于这个IDE的使用方法比较少人讲述 xff0c 兴许使用的人比较少吧 另一个类似的IDE是CS 43 for cc
  • 单片机——按键扫描

    按键扫描 xff0c 我想应该是比较简单的单片机应用了 xff0c 但是有时候看起来简单的东西反而不好写 本文拿大部分人觉得简单的按键扫描聊聊我工作至今对于软件结构的理解 嗯 xff0c 对的 xff0c 是结构 xff0c 不是架构 xf