单片机--按键定时器检测:短按、长按

16 篇文章 4 订阅


 通过使用定时器计数的方法来分辨按键的:短按、长按

  1. 检测到引脚被拉低:按键按下为低电平,没有按下为高电平
  2. 延时10毫秒:滤波
  3. 引脚还是被拉低:确定按键被按下
  4. 设置按键按下标志
  5. 开启定时器,开始计数:定时器中有一个全局变量用于记录计数值
  6. 直到引脚被拉高:按键被释放将为高电平
  7. 关闭定时器
  8. 检测按键按下标志
  9. 检测定时器按键检测时间全局变量是否大于某个值,一般为200ms
  10. 大于则为长按,否则为短按

一、51系列单片机按键检测

typedef enum
{
	KEY_SINGLE_PRESSED,
	KEY_LONG_PRESSED,
	KEY_DEFAULT_STATUS,
} key_state_e;

uint8_t ylf_key_scan(void)
{
	static uint8_t press_flag;

	if(!KEY_PIN)
	{
		delay_ms(10);
		if (!KEY_PIN)
		{
			press_flag = 1;
			TR0 = 1;           // 定时器0开始计数
			while(!KEY_PIN);
			TR0 = 0;           // 定时器0计数结束
		}
	}
	if (press_flag)
	{
		if (KEY_COUNT >= 200)
		{	
			KEY_COUNT = 0;
			press_flag = 0;			
			return KEY_LONG_PRESSED;

		} else {		
			KEY_COUNT = 0;
			press_flag = 0;	
			return KEY_SINGLE_PRESSED;
		}
	}
	return KEY_DEFAULT_STATUS;
}


int main(void)
{
	
	while(1)
	{
		switch(ylf_key_scan())
		{
		case KEY_SINGLE_PRESSED:
			// TO DO
			break;
		case KEY_LONG_PRESSED:
			// TO DO
			break;
		default:
			break;
		}
	}
}

二、多定时器按键检测

条件:

  • 支持软件定时器
    • 软件定时器开始:app_timer_start(timer_id)
    • 软件定时器结束:app_timer_stop(timer_id)

思路:需要单片机支持引脚上下沿触发,通过使用两个定时器,来达到目的,具体代码如下:

APP_TIMER(TIMER_LONG_PRESS_ID); // 创建定时器ID
APP_TIMER(TIMER_DEBOUNCE_PRESS_ID); // 创建定时器ID

// 长按处理函数
void key_long_press_handler(void)
{
	if(KEY_PIN == 0) // 超过100ms还是低电平意味着是长按
	{
	 	// 长按标准
	}
	else if(KEY_PIN == 1) // 已经释放掉意味着是短按
	{
		// 短按标准
	}
}

// 按键消抖处理函数
void key_debounce_handler(void)
{
	if(KEY_PIN == 0) // 消抖之后还是低电平意味着确实是有按下
	{
		// 开启长按定时器:100ms
		app_timer_start(TIMER_LONG_PRESS_ID, 100, key_long_press_handler);
	}
}

// 按键触发处理函数
void key_toggle_handler(void)
{
	if(KEY_PIN == 0)
	{
		// 开启消抖定时器:10ms
		app_timer_start(TIMER_DEBOUNCE_PRESS_ID, 10, key_debounce_handler);
	}
}

void key_init(void)
{
	// 设置按键引脚,下降沿触发,设置触发处理函数
	gpio_toggle_config(KEY_PIN, TOGGLE_UPTODOWN, key_toggle_handler);
}


三、轮询方式按键检测–根据时间戳

思路解释如下:按键状态结构体有一个用于识别的状态位,默认处于Release,也就是释放的状态。一旦按键被按下,中断触发,此时检查是否是Relase状态,如果是就检查按键是否被拉低,如果是,此时进入May_Press状态,也就是可能是按下的,并且记录此时的时间戳,这一步是消抖的关键。当按键被释放,由于是边沿触发,会再次进行处理,此时检查和上一次触发之间的时间戳之差,如果小于10ms我们就认为是抖动,此时不会对按键输出状态进行修改,而是直接将按键状态置回Relase状态,反之检查差值和长短按阈值之间的关系,将state置位为对应的状态。消抖的核心在于记录时间戳,而这只是一个简单的赋值操作,并不耗费时间。

效率上来说,延时消抖花费时间在无意义延时上,而相对较好的定时轮询还是不可避免的在轮询,而现在这种方式完全是中断性质的。唯一多出的开销(全局时间戳)并不是只可以用于按键消抖,另外在HAL库中存在直接获取tick的函数,这样实现就更方便了。

第一步:初始化全局时间戳的定时器,一般采用系统滴答定时器来产生,每1ms一次即可。

第二步:初始化按键对应的IO,复用为边沿触发的外部中断。

第三步:在外部中断函数中添加按键事件处理函数。

typedef struct _key_state_t  
{  
  uint32_t key_time;  
  enum  
  {  
    MAY_PRESS,  
    RELEASE,  
  } current_state;  
  enum  
  {  
    NO_PRESS,  
    SHORT_PRESS,  
    LONG_PRESS,  
  } state;  
} key_state_t;

#define SHORTPRESS_THRESHOLD  1500

鉴于评论区的意见改进了一下:

key_state_t key_state = {
	.key_time      = 0,
	.current_state = RELEASE,
	.state         = NO_PRESS,
};

// 1ms 定时器中断服务函数
void Timer0_IRQ_Handler(void)
{
	...

	if(key_state.current_state == RELEASE)          
	{  
		if(KEY_PIN == 0)  // 按键被按下
	    {  
	        key_state.current_state = MAY_PRESS;  
	        key_state.key_time = 0; 
	    }  
	}  
	else if(key_state.current_state == MAY_PRESS)  
	{  
		key_state.key_time++; // 累加每次为1ms
		
		if(KEY_PIN == 1)  // 按键释放
		{  
			// 由释放时的时间长短区分出长按与短按
			if((key_state.key_time > 10)&&(key_state.key_time < SHORTPRESS_THRESHOLD))
			{  
				key_state.state = SHORT_PRESS;  // 按下时间为:10ms~1500ms 判定为短按
				key_state.current_state = RELEASE;  
			}  
			else if(key_state.key_time > SHORTPRESS_THRESHOLD)  
			{  
				key_state.state = LONG_PRESS;  // 按下时间超过1500ms判定为长按
				key_state.current_state = RELEASE;  
			}  
			else // 按下时间小于10ms就释放视为杂波
			{
				key_state.current_state = RELEASE; 
			} 	  
		}  
	}
	...
}


if(key_state.current_state == RELEASE)          
{  
	if(KEY == 0)  // 按键被按下
    {  
        key_state.current_state = MAY_PRESS;  
        key_state.key_time = course_ms();  // 记录当前时间戳
    }  
}  
else if(key_state.current_state == MAY_PRESS)  
{  
	if(KEY == 1)  // 按键释放
	{  
		// 由释放时的时间戳区分出长按与短按
		if((course_ms() - key_state.key_time > 10)&&(course_ms() - key_state.key_time < SHORTPRESS_THRESHOLD))
		{  
			key_state.state = SHORT_PRESS;  
			key_state.current_state = RELEASE;  
		}  
		else if(course_ms()-key_state.key_time > SHORTPRESS_THRESHOLD)  
		{  
			key_state.state = LONG_PRESS;  
			key_state.current_state = RELEASE;  
		}  
		else 
		{
			key_state.current_state = RELEASE;
		} 	  
	}  
}

以上代码需要添加到中断处理函数的按键事件处理逻辑中,算法的核心是一个状态机。按键被默认上拉,按下接地。course_ms()为获取全局时间戳的函数。

总结

 以上就是目前我用过的所有类型的按键检测方法。

STM32实现定时器按键检测通常涉及到使用STM32的硬件定时器和GPIO(通用输入输出)引脚。按键检测通常需要消抖处理,以避免由于按键机械弹性造成的多次触发。以下是实现STM32定时器按键检测的基本步骤: 1. 初始化按键对应的GPIO引脚为输入模式,并设置为上拉或下拉输入,以确保没有按键动作时输入是稳定的。 2. 设置硬件定时器,配置合适的时钟源和分频,使定时器按照需要的频率产生中断。 3. 在定时器中断服务程序中编写消抖逻辑。每次定时器中断触发时,检查按键的当前状态。如果检测按键状态有变化,启动一个延时计数器。 4. 在延时期间,如果检测按键状态再次发生变化,则认为是干扰,重置延时计数器。如果延时结束后按键状态仍然保持,则认为按键确实被按下。 5. 在确认按键按下后,可以执行相应的处理程序,比如改变某个标志位、触发某个事件等。 6. 重置定时器,以便再次开始新的按键检测周期。 下面是一个简单的伪代码示例,展示了如何实现一个基本的按键检测: ```c volatile uint32_t debounceTimer = 0; void GPIO_Config(void) { // 配置GPIO为输入模式 // 初始化上拉或下拉电阻 } void TIM_Config(void) { // 初始化定时器,设置中断频率 // 使能定时器中断 } void TIMx_IRQHandler(void) { if (/* 定时器中断标志 */) { // 清除中断标志 if (/* 检测按键状态变化 */) { debounceTimer = DEBOUNCE_TIME; // 启动消抖计时器 } else if (debounceTimer > 0) { debounceTimer--; // 计时器递减 } if (debounceTimer == 0) { if (/* 检测按键稳定按下 */) { // 按键按下处理逻辑 } } } } int main(void) { // 系统初始化 // GPIO_Config(); // TIM_Config(); while (1) { // 主循环,其他任务执行 } } ```
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值