基于状态机思想的按键扫描程序支持单按,连续按,长按模式。

typedef enum {
	ButtonsStateUp           = 0,
	ButtonsStateDownJitter      ,
	ButtonsStateDown            ,
	ButtonsStateUpJitter
} EnumButtonsState;

typedef unsigned char uint8;
typedef unsigned long uint32;

#define BUTTONS_UP    0
#defien BUTTONS_DOWN  1

#define BUTTONS_EVENT_NONE           0x00
#define BUTTONS_EVENT_SINGLE_DOWN    0x01
#define BUTTONS_EVENT_SERIES_DOWN    0x02
#define BUTTONS_EVENT_CONTINUE_DOWN  0x03
#define BUTTONS_EVENT_UP             0x04

EnumButtonsState ButtonsState;

void ButtonsScanSM(uint8 *event_type)
{
	uint8 buttons_status;
	
	static uint32 time_point        = 0;
	static uint8  series_down_flg   = 0;
	
	buttons_status = GetButtonsStatus();
	
	switch(ButtonsState)
	{
		case ButtonsStateUp:
		{		
			if(GetSysTicks() - time_point > 1000u)
			{
				series_down_flg = 0;
			}
			
			if(buttons_status == BUTTONS_UP)
			{
				;
			}
			else
			{
				ButtonsState = ButtonsStateDownJitter;
				time_point   = GetSysTicks();
		
<think>好的,我现在需要帮用户解决如何不使用延时来实现按键按、短按、按和多次按的问题。用户之前已经讨论过结构体数组和指针的相关问题,现在转向按键处理逻辑,可能是在嵌入式开发中遇到实际应用需求。 首先,我得回忆一下常见的按键检测方法。传统的方法可能会用延时来消抖,但用户明确要求不使用延时,所以必须采用非阻塞的方式。通常这可以通过定时器中断或者状态机来实现。 接下来,用户提到的按键事件包括按、短按、按、多次按。这些事件需要不同的时间判断,比如短按可能在100ms内释放,按超过500ms等。同时,多次按需要检测两次按键之间的时间间隔。 考虑到用户之前代码中的结构体KeyState,里面包含了Key_Cnt计数器和Key_Flag状态,这可能用于记录按键的时间。可能需要利用定时器定期更新Key_Cnt的值,而不是用延时。 状态机的设计是关键。每个按键需要维护自己的状态,比如释放、按下、按检测等。可能的状态包括: - 释放状态:等待按键按下 - 按下状态:记录按下时间,判断是否达到按阈值 - 等待释放状态:短按后等待释放,同时检测是否再次按下以判断多次按 定时器的使用是必须的。假设系统有一个定时器中断,每5ms或10ms触发一次,更新按键的状态。这样可以在每次中断中检测按键的电平变化,并更新计数器。 消抖处理需要在定时器中进行。例如,连续多次检测到按键按下才认为有效,避免毛刺干扰。可以使用计数器,当检测到按键按下时递增,释放时递减,达到一定阈值才改变状态。 然后需要考虑如何区分不同的按键事件: - 击:按下后在短时间内释放,且没有再次按下 - 双击或多击:两次按下之间的时间间隔在特定范围内 - 按:按下时间超过阈值后持续保持 这里可能需要记录每次按下和释放的时间间隔,并根据这些时间来判断事件类型。 对于多次按的检测,需要记录两次按下的时间间隔是否在允许的范围内。例如,双击可能要求两次按下之间的时间不超过300ms。 另外,需要考虑按键释放后的状态转换,比如在按释放后应该触发按事件,而不是短按。 可能的结构体需要扩展,比如添加用于记录时间的变量,或者当前状态的枚举。例如: typedef enum { KEY_RELEASED, KEY_PRESSED, KEY_LONG_PRESS, KEY_WAIT_RELEASE, } KeyState; 但需要结合用户之前的KeyState结构体,可能需要添加额外的字段如last_time, press_time, event等。 在定时器中断服务程序中,每个周期读取按键的当前状态,进行消抖处理,然后根据状态机转换状态,并记录事件。 例如: 1. 初始状态为KEY_RELEASED。 2. 当检测到按键按下且消抖后,记录按下时间,并进入KEY_PRESSED状态。 3. 如果持续按下超过按阈值,则触发按事件,并进入KEY_LONG_PRESS状态。 4. 当按键释放时,根据按下时间判断是否为短按,并进入KEY_WAIT_RELEASE状态,同时启动计时以判断是否双击。 5. 在KEY_WAIT_RELEASE状态中,如果再次按下,增加点击次数,并重新计时。 需要注意的是,所有的时间判断都是基于定时器的累计,而不是使用延时函数,这样保证了非阻塞。 用户可能需要具体的代码示例,所以需要提供一个状态机的实现框架,包括结构体定义、定时器回调函数、状态转换逻辑等。 另外,需要提醒用户注意硬件消抖和软件消抖的结合,定时器的频率选择(如5ms),以及如何避免多次触发事件。 可能还需要处理多个按键的情况,每个按键独立维护状态。 总结步骤: 1. 定义按键状态机结构体,包含状态、计数器、事件标志等。 2. 在定时器中断中定期读取按键电平,进行消抖。 3. 根据当前状态和按键电平变化,更新状态和事件。 4. 在主循环或任务中检查事件标志,执行对应操作。 现在需要将这些思路整理成结构化的回答,并给出代码示例,同时提出相关问题。 </think>要实现按键按、短按、按、多次按而不使用延时,需通过**状态机+定时器**的非阻塞方式处理。以下是完整实现方案: --- ### 1. **核心原理** - **定时器中断**:每5ms触发一次,用于按键消抖和状态更新。 - **状态机**:跟踪按键的按下、释放、按、多次按等状态。 - **时间计数**:通过定时器累积时间判断事件类型。 --- ### 2. **数据结构定义** ```c typedef enum { KEY_IDLE, // 空闲(释放状态) KEY_PRESSED, // 按下(消抖完成) KEY_HOLD, // 按检测 KEY_RELEASE_WAIT // 释放后等待双击 } KeyState; typedef struct { KeyState state; // 当前状态 uint16_t press_time; // 按下时间计数(位:ms) uint16_t release_time; // 释放时间计数(双击检测) uint8_t click_count; // 点击次数 bool event; // 事件标志(需外部清零) } KeyHandler; // 示例全局变量 KeyHandler key1_handler = {0}; ``` --- ### 3. **定时器中断服务程序** ```c void SysTick_Handler(void) { // 假设定时器每5ms触发一次 static uint8_t key_pin = 0; // 假设读取按键硬件状态 KeyHandler* handler = &key1_handler; switch(handler->state) { case KEY_IDLE: if (!KEY1) { // 检测到按下(低电平有效) handler->state = KEY_PRESSED; handler->press_time = 0; } break; case KEY_PRESSED: handler->press_time += 5; // 累计时间 if (KEY1) { // 检测到释放 if (handler->press_time < 200) { // 短按阈值 handler->click_count++; handler->state = KEY_RELEASE_WAIT; handler->release_time = 0; } handler->state = KEY_IDLE; } else if (handler->press_time >= 1000) { // 按阈值 handler->event = true; // 触发按事件 handler->state = KEY_HOLD; } break; case KEY_HOLD: if (KEY1) { // 释放按键 handler->state = KEY_IDLE; } break; case KEY_RELEASE_WAIT: handler->release_time += 5; if (KEY1) { // 释放后重新进入空闲 handler->state = KEY_IDLE; } else if (handler->release_time >= 300) { // 双击超时 handler->event = true; // 击事件 handler->click_count = 0; handler->state = KEY_IDLE; } break; } } ``` --- ### 4. **主循环事件处理** ```c int main(void) { while (1) { if (key1_handler.event) { if (key1_handler.click_count == 1) { // 击事件 } else if (key1_handler.click_count == 2) { // 双击事件 } // 按事件(在中断中已触发) key1_handler.event = false; // 清除事件标志 } } } ``` --- ### 5. **关键参数说明** | 事件类型 | 时间阈值 | 说明 | |----------|---------|-----| | 短按 | <200ms | 按键按下后快速释放 | | 按 | ≥1000ms | 按键持续按下超过阈值 | | 双击间隔 | ≤300ms | 两次点击之间的最大间隔 | 可根据实际需求调整阈值。 --- ### 6. **优势与注意事项** - **非阻塞**:完全依赖定时器中断处理,不占用主循环。 - **多按键扩展**:为每个按键分配独立的`KeyHandler`结构体。 - **硬件兼容性**:需确保按键电平检测逻辑(如`KEY1`宏定义)与硬件一致。 - **消抖策略**:示例中使用定时器累计时间消抖,也可结合硬件RC滤波。 --- ### 7. **常见问题** - **Q1**:如何区分双击和两次击? **A1**:在`KEY_RELEASE_WAIT`状态中,若两次按下间隔超过300ms,则视为两次击。 - **Q2**:如何避免按事件重复触发? **A2**:在按状态(`KEY_HOLD`)中,仅在进入时触发一次事件,后续保持不重复触发。 - **Q3**:如何支持更多按键? **A3**:为每个按键创建独立的`KeyHandler`实例,并在定时器中断中分别处理。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值