定时器扫描按键(短按/长按)


前言

  本次编程实验以IAP15F2K61S2为单片机主控芯片,其编程使用与STC15F2K60S2完全相同,头文件为STC15F2K60S2.H。若用于51系列单片机,以reg52.h为头文件,则需将后文中timer0.c中void Timer0_Init(void)函数中的AUXR &= 0x7F;一行删除。程序中涉及的定时器初始化程序和LED亮灭程序和数码管显示程序,读者可根据自身所用单片机原理图和手册自行修改。


一、为什么使用定时器扫描按键?

  在对主程序执行效率要求低、项目工程复杂度低的情况下,通常可以使用以下代码检测按键:

void key()
{
	if(key==0)   //如果按键按下
	{
		delay(20);   //延时消除抖动
		if(key==0)    //确认按键按下
		{
			while(key==0);   //等待按键松开	
		}
	}
}

  当按键按下迟迟不松开时,主程序一直卡在while(key==0);里,变相停止了主程序向下运行,影响程序正常执行。为此我们考虑使用定时器间隔扫描按键的方式实现按键检测。

二、定时器扫描按键的原理

1.基本原理

  为了检测按键按下和松开,必须记录按键当前状态和上一次状态。如果当前状态为高电平,上一次状态为低电平,则检测到按键松手;如果当前状态为低电平,上一次为高电平,则检测到按键按下。由于短按按下一次只需返回一次按键值,因此只需检测按键松开动作,即上一次状态低电平,当前状态高电平。

2.消抖原理

  按键按下和松开时,其连接的IO口电平并非瞬间拉低,而是会有一小段时间的抖动:

按键抖动
  考虑到按键按下和松开抖动时间较短,按下时间通常大于50ms。定时器每隔25ms扫描一次按键状态,假设扫描时恰好处于按键按下时的抖动过程,检测到低电平,那么经过25ms此时按键已经稳定处于按下状态,必然检测到低电平,从而上一次状态和现状态均为低电平;如果检测到高电平,则25ms后,上一次状态为高电平,当前状态为低电平,再经过25ms后,上一次状态和现状态均为低电平。当按键松开时,假设恰好检测到抖动过程的高电平,则上一次状态为低电平,当前状态为高电平,符合松手条件,返回按键值;如果检测到抖动过程的低电平,则上一次状态和当前状态均为低电平,经过25ms后,此时按键稳定处于松开状态,必然检测到高电平,则上一次状态为低电平,当前状态为高电平,符合松手条件,返回按键值。

三、编程实现

1.独立按键函数

  key.h:

#ifndef __KEY_H
#define __KEY_H

#include <STC15F2K60S2.H>

sbit key1=P3^0;  //根据原理图上独立按键连接的IO口定义
sbit key2=P3^1;
sbit key3=P3^2;
sbit key4=P3^3;

unsigned char key_state();  //按键状态获取
unsigned char key_send(unsigned char mode);  //按键值返回
#endif

  key.c:
  mode用于选择短按长按模式。mode为0 时,通过检测是否满足上一次状态为低电平(keylast不为0)且当前状态为高电平(keynow为0)决定是否返回按键值;mode为1时,通过检测是否满足上一次状态为低电平(keylast不为0)且当前状态为低电平(keynow不为0,且keylast=keynow)决定是否返回按键值。

#include "key.h"
unsigned char key_state()  //得到按键状态
{
	unsigned char keyget=0;
	
	if(!key1)keyget=1;  //如果按键1按下
	if(!key2)keyget=2;  //如果按键2按下
	if(!key3)keyget=3;  //如果按键3按下
	if(!key4)keyget=4;  //如果按键4按下
	
	return keyget;   //返回按键状态
}

/*
  *  @brief     按键值发送函数
  *  @param     mode:按键长短按模式标志,1长按0短按
*  @reval       keyend:最终返回的按键值
  *  @note    
*/

unsigned char key_send(unsigned char mode)  
{
	static unsigned char keynow=0,keylast=0; //按键当前状态;按键上一次状态
	unsigned char keyend=0;            //最终要发送的按键值,每次扫描按键时均清零
	
	keylast=keynow;       //不断刷新按键上一次状态和当前状态
	keynow=key_state();
	if(keylast==1&&keynow==(1*mode))  //如果按键1按下
		keyend=1;
	if(keylast==2&&keynow==(2*mode))  //如果按键2按下
		keyend=2;
	if(keylast==3&&keynow==(3*mode))  //如果按键3按下
		keyend=3;
	if(keylast==4&&keynow==(4*mode))  //如果按键4按下
		keyend=4;
	
	return keyend;   //发送最终按键值
}

2.定时器部分函数

  timer0.h:

#ifndef __TIMER0_H
#define __TIMER0_H

#include <STC15F2K60S2.H>

void Timer0_Init(void);		//1毫秒@11.0592MHz

#endif

  timer0.c:

#include "timer0.h"

void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	AUXR &= 0x7F;			//定时器时钟12T模式,若以reg52.h为头文件时删除这行代码
	TMOD &= 0xF0;			//设置定时器模式
	TMOD |= 0x01;			//设置定时器模式
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	
	ET0 = 1;				//使能定时器0中断
	EA=1;                   //使能总中断
}

3.main.c

  定时器定时1毫秒基础时间(方便扩展其他时间要求的功能函数),每过25毫秒检测一次按键,如果按键按下并松开,则返回相应的按键值(只监测按键松手,因此按下一次只能返回一次按键值);如果连续按下2秒,则切换为长按模式,每过500毫秒检测一次按键,并返回按键值(监测按键持续按下,每过500毫秒返回一次按键值)。
  实际上,短按扫描是一直在运行的。在短按模式起返回按键和检测按下时间是否超过2s作用;在长按模式起检测按键是否松手作用。如果在长按模式期间不进行短按扫描,那么一旦进入长按模式就无法返回短按模式。

#include <STC15F2K60S2.H>
#include "key.h"
#include "timer0.h"

typedef unsigned char u8;
#define outputp0(y,x)  P0=x,P2&=0x1f,P2|=y,P2&=0x1f; //P2高三位用于74HC138译码器选择锁存器,P0用于发送数据
u8 keynum=0,keytemp=0,light=0xff;
int showkey=0;
void test()  //测试程序
{
	if(keytemp!=0)
		keynum=keytemp;  //通过缓存量向keynum赋值,防止按下按键松开后立刻被清零
	if(keynum!=0)
	{
		light=0xff<<keynum;
		outputp0(0x80,light);   //选择数据锁存器通道,向LED灯发送数据
		keynum=0;      //清零,防止一次按下多次执行
		keytemp=0;
	}
}
void main()
{ 
	Timer0_Init();	
	while(1)
	{
		test();
	}
}

void Timer0_Isr(void) interrupt 1
{
	static u8 longflag=0;      //长短按模式标志,1为长按,0为短按
	static int tkey=0,tlongflag=0,tlongkey=0; //短按时间计数;按键按下时间计数;长按时间计数
	TL0 = 0x66;				//设置定时初始值  //1ms
	TH0 = 0xFC;				//设置定时初始值
	
	tkey++;          //按键短按扫描时间计数
	if(tkey>=25)   //1.短按每25ms检测一次按键;2.作长时间按下监测
	{
		if(!(key1&key2&key3&key4))     //长按检测,每25ms检测按键是否仍在按下,如果是
		{
			tlongflag++;			//按键按下时间计数(以25ms为单位)
			if(tlongflag>=80)       //如果持续按下2s
			{
				tlongflag=80;         //防止溢出
				longflag=1;           //切换为长按模式
			}
		}
		else if(key1&key2&key3&key4)	//长按检测,如果2s内任意一个25ms时间点检测时按键不按下
		{
			tlongflag=0;				//清零按键按下时间计数
			longflag=0;					//切换为短按模式
		}
		
		tkey=0;             //清零短按扫描时间计数
		if(longflag==0)         //如果是短按模式
			keytemp=key_send(0);  //按键以短按模式检测
	}
	
	tlongkey++;           //按键长按扫描时间计数
	if(tlongkey>=500)     //长按时每500ms检测一次按键
	{
		tlongkey=0;				 //清零按键长按扫描时间计数
		if(longflag==1)         //如果是长按模式
			keytemp=key_send(1);  //按键以长按模式检测
	}
}

四、测试程序现象

  测试程序现象:按键1按下并松开,LED1亮;按键2按下并松开,LED1、2亮;按键3按下并松开,LED1、2、3亮;按键4按下并松开,LED1、2、3、4亮。
  上述测试程序证明了按键值可以正常读取,但是并未检验按键扫描不会卡住主程序,也未检验按键长按时的成功性。为此,我们需要加上数码管对其进行检验。
  向main.c文件中添加以下代码:

#include <intrins.h>

//注意变量定义应放在main.c文件的开头部分
int shownum=0;  //数码管显示数字
unsigned char code Seg_Table[]={
0xc0,     //共阳数码管段码表
0xf9,
0xa4,
0xb0,
0x99,
0x92,
0x82,
0xf8,
0x80,
0x90,
0xff  //0xff熄灭
};

void Delay2ms(void)	//2ms@11.0592MHz
{
	unsigned char data i, j;
	_nop_();
	_nop_();
	i = 22;
	j = 128;
	do
	{
		while (--j);
	} while (--i);
}

void showbit(unsigned char pos,dat,dot)  //数码管显示函数(1位)
{
	outputp0(0xc0,0x01<<pos);    //向数码管位选端发送数据
	outputp0(0xe0,Seg_Table[10]);  //向数码管段选端发送数据,这两行代码起消影作用
	
	outputp0(0xc0,0x01<<pos);     //向数码管位选端发送数据
	outputp0(0xe0,Seg_Table[dat]+0x80*dot);	  //向数码管段选端发送数据
	Delay2ms();   //延时
}

  将main.c中的test()函数更改为:

void test()
{
	keynum=keytemp;  //通过缓存量向keynum赋值,防止按下按键松开后立刻被清零
	if(keynum)
	{
		light=0xff<<keynum;    //LED部分
		outputp0(0x80,light);
		shownum+=keynum;
		
   	  keynum=0;
	  keytemp=0;
	}	
	showbit(0,shownum/100%10,0);  //数码管显示部分
	showbit(1,shownum/10%10,0);
	showbit(2,shownum%10,0);
}

  测试现象:短按时,按键1按下并松开,LED1亮;按键2按下并松开,LED1、2亮;按键3按下并松开,LED1、2、3亮;按键4按下并松开,LED1、2、3、4亮。数码管显示数字根据按键返回值而增加。长按时,按键按下2s后开始有现象:LED点亮(与短按相同),数码管显示值从按下2s时开始每隔500ms变化一次。

五、总结

  通过上述程序,可以实现定时器扫描按键以提高主程序执行效率。通过类比,也可以实现定时器扫描矩阵键盘和数码管,详情见定时器扫描矩阵键盘简易代码(短按/长按)定时器扫描8位数码管(含滚动显示)
  此外,由于定时器每1ms进入一次中断,对于具有IIC和One-Wire通信等具有严格时序要求的项目来说,容易使通信被干扰,因此不建议使用定时器扫描的方式。当然,通过一些方法也可以实现二者兼容并存,在DS1302可调时钟(按键短按/长按)中提供了其中一种方法。

  有任何问题和补充,欢迎评论区交流。

在使用HAL定时器进行按键检测时,可以采用轮询模式或中断模式。在轮询模式下,可以使用HAL_TIM_Base_Start函数启动定时器,然后在while循环中不断检测按键状态。这种方式需要在每次检测按键之间添加适当的延时来进行消抖,以防止误检测。然而,这种方式会时间占用单片机的CPU资源,效率较低。\[3\] 另一种方式是使用外部中断加定时器的方式实现按键检测。首先,需要设置外部中断触发方式,并计算预分频系数psc和计数值大小arr来确定定时器的时间。然后,在外部中断回调函数中打开定时器,让其开始计数。当定时器溢出时,关闭定时器,并再次检测按键IO口的电平值。这种方式可以消除按键的抖动,并且不会时间占用CPU资源,提高了处理器的效率。\[1\] 另外,还可以使用状态机加定时器中断的方式检测按键。将状态切换的代码写在定时器中断服务回调函数中,在主函数中判断按键的有效状态,并发送脉冲来获取计数值。这种方式也可以消除按键的抖动,并提高处理器的效率。\[2\] #### 引用[.reference_title] - *1* *3* [STM32 CubeMx HAL库外部中断检测按键定时器延时消抖](https://blog.csdn.net/DIVIDADA/article/details/128364061)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [HAL学习笔记 - 7 定时器之基本定时器](https://blog.csdn.net/kouqi627/article/details/115353077)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值