【蓝桥杯嵌入式】长短按 按键

        在上一章节“【蓝桥杯嵌入式】状态机按键”中使用的按键程序中,按键间具有优先性(如B1按下后,B2~B4的按下将不会被识别),同时不支持双键、长短按。在这一章节中,我们升级一下,采用独立的按键检测,并实现下长短按。

一、按键的独立化

        我们针对每一个按键都进行了独立的状态检测和处理,定义了一个结构体,一个数组用来存储按键的不同信息。

typedef enum 
{
    IDLE,               // 未按下,空闲状态
    PRESSED_READY,      // 按下,待执行相应功能
    PRESSED_FINISH,     // 按下,功能已执行,等待松开按键回到空闲状态
    PRESSED_COUNT       // 按下,处于计时状态(用于长短按)
} ButtonState;          // 按键状态

// 定义一个函数类型
typedef void (*Function)(void);

typedef struct
{
    GPIO_TypeDef *port;         // 按键对应的GPIO端口
    uint16_t pin;               // 按键对应的引脚
    ButtonState key_state;      // 按键的状态(空闲、按下、计时等)
    unsigned char long_mode;    // 按键长按模式(用于标记长按模式,1为长短按键)
    unsigned int pressed_time;  // 按键按下的持续时间(用于长按计时)
    Function keyFunction;       // 按键短按功能对应的函数指针
    Function keyFunctionLong;   // 按键长按功能对应的函数指针
} key_GPIO;                     // 按键对应信息

// 按键对应的IO口信息及功能回调函数
key_GPIO key[4];

函数指针允许我们将函数的地址存储在结构体的成员中,以便在需要时调用相应的函数。我们定义了函数指针类型Function ,用于存储按键的短按功能和长按功能对应的函数地址。在初始化 key_GPIO 结构体数组时,为每个按键的 keyFunctionkeyFunctionLong 成员赋值为相应的函数名。这样,每个按键的结构体中就包含了对应的函数指针,可以根据按键的状态调用相应的功能函数。

// 按键对应的IO口信息及功能回调函数
key_GPIO key[4] = {
    {GPIOB,GPIO_PIN_0,IDLE,0,0,function_B1},
    {GPIOB,GPIO_PIN_1,IDLE,0,0,function_B2},
    {GPIOB,GPIO_PIN_2,IDLE,0,0,function_B3},
    {GPIOA,GPIO_PIN_0,IDLE,1,0,function_B4,function_B4_long}
};

按键的功能与上一章节基本一致,只是把B4按键改成了一个长短按键,短按:翻转LD7状态,长按:熄灭LD1~LD7。

// 按键B1的功能实现
void function_B1()
{
    led_addr = led_addr | 0x03; // 更新LED位置
    led_disp(led_addr);         // 控制LED显示
}

// 按键B2的功能实现
void function_B2()
{
    led_addr = led_addr | 0x0C; // 更新LED位置
    led_disp(led_addr);         // 控制LED显示
}

// 按键B3的功能实现
void function_B3()
{
    led_addr = led_addr ^ 0x30; // 更新LED位置
    led_disp(led_addr);         // 控制LED显示
}

// 按键B4的功能实现
void function_B4()
{
    led_addr = led_addr ^ 0x40; // 更新LED位置
    led_disp(led_addr);         // 控制LED显示
}

// 按键B4长按的功能实现
void function_B4_long()
{
    led_addr = led_addr & 0x80; // 更新LED位置
    led_disp(led_addr);         // 控制LED显示
}

二、状态机程序

与之前的状态机类似,检测都是满足一定条件就进入下一个状态

检测按键当前的状态(key_state):

  • 如果是空闲状态(IDLE),检测是否有按键按下,若有则将状态切换为 PRESSED_READY
  • 如果是按下待执行状态(PRESSED_READY),继续检测按键是否仍然被按下,根据按键类型执行相应的操作。
  • 如果是按下已执行状态(PRESSED_FINISH),检测按键是否已经松开,若是则将状态切换回 IDLE

不同的是:

1、每个按键都有各自的状态标签,每次根据不同按键的状态执行对应的步骤,实现独立化(按下B1不松开执行B1功能,再按下B2(此时B1、B2都处于按下状态)能够执行B2功能);

2、B4为长短按功能,执行长短按检测流程:

  • 如果按键处于空闲状态(IDLE),检测是否被按下,如果按下则切换到按下待执行状态(PRESSED_READY)。
  • 如果按键处于按下待执行状态(PRESSED_READY),检测是否仍然被按下:
    • 如果按键仍然被按下:
      • 如果按键为长按键(long_mode为1),则切换到计时状态(PRESSED_COUNT)进行长按计时;
    • 如果按键松开(抖动),则切换回空闲状态(IDLE)。
  • 如果按键处于计时状态(PRESSED_COUNT),对按键进行长按计时,当计时达到长按阈值时执行长按功能,并切换到按下已执行状态(PRESSED_FINISH)。如果未达阈值但按键松开,按下时间小于短按阈值,则执行短按功能并切换回空闲状态(IDLE)。
  • 如果按键处于按下已执行状态(PRESSED_FINISH),检测按键是否松开,如果松开则切换回空闲状态(IDLE)。
unsigned int i = 0; // 声明变量 i,用于循环计数

// 遍历按键数组
for(i = 0 ; i < 4 ; i++)
{
		switch(key[i].key_state)
		{
				case IDLE:
						// 如果按键被按下,切换到 PRESSED_READY 状态
						if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_RESET)
						{
								key[i].key_state = PRESSED_READY;
						}
						break;
						
				case PRESSED_READY:
						// 如果按键仍然被按下,根据按键类型执行相应的功能
						if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_RESET)
						{
								if(key[i].long_mode == 1)
								{   // 长按键,需要计时
										key[i].key_state = PRESSED_COUNT;
								}
								else
								{   // 其他按键直接执行功能并切换状态到 PRESSED_FINISH
										key[i].keyFunction();
										key[i].key_state = PRESSED_FINISH;
								}                       
						}
						else 
						{
								// 按键松开,切换回 IDLE 状态
								key[i].key_state = IDLE;
						}
						break;
						
				case PRESSED_FINISH:
						// 检测按键是否松开,如果是则切换回 IDLE 状态
						if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_SET)
						{
								key[i].key_state = IDLE;
						}                       
						break;
						
				case PRESSED_COUNT:
						// 对长按键进行计时
						key[i].pressed_time ++;
						if(key[i].pressed_time >= 100)
						{   
								// 如果长按键计时达到阈值,则执行长按功能并切换状态到 PRESSED_FINISH
								key[i].keyFunctionLong();
								key[i].pressed_time = 0;
								key[i].key_state = PRESSED_FINISH;
						}
						else if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_SET)
						{   
								// 如果长按键松开,根据长按时间决定是否执行短按功能,并切换回 IDLE 状态
								if(key[i].pressed_time <= 50)
								{
										key[i].keyFunction();
								}
								key[i].pressed_time = 0;
								key[i].key_state = IDLE;
						}
						break;
						
				default:
						// 默认情况下切换回 IDLE 状态
						key[i].key_state = IDLE;
						break;
		}

三、整体程序

interrupt.c中的完整代码如下:

#include "interrupt.h"  // 包含中断处理相关的头文件
#include "led.h"        // 包含LED控制相关的头文件

void function_B1(void);    		// 函数声明:按键B1的功能
void function_B2(void);    		// 函数声明:按键B2的功能
void function_B3(void);    		// 函数声明:按键B3的功能
void function_B4(void);    		// 函数声明:按键B4短按的功能
void function_B4_long(void); 	// 函数声明:按键B4长按的功能

static unsigned char led_addr = 0x00;  // 用于记录亮灯的位置

typedef enum 
{
    IDLE,               // 未按下,空闲状态
    PRESSED_READY,      // 按下,待执行相应功能
    PRESSED_FINISH,     // 按下,功能已执行,等待松开按键回到空闲状态
    PRESSED_COUNT       // 按下,处于计时状态(用于长短按)
} ButtonState;          // 按键状态

// 定义一个函数类型
typedef void (*Function)(void);

typedef struct
{
    GPIO_TypeDef *port;         // 按键对应的GPIO端口
    uint16_t pin;               // 按键对应的引脚
    ButtonState key_state;      // 按键的状态(空闲、按下、计时等)
    unsigned char long_mode;    // 按键长按模式(用于标记长按模式,1为长短按键)
    unsigned int pressed_time;  // 按键按下的持续时间(用于长按计时)
    Function keyFunction;       // 按键短按功能对应的函数指针
    Function keyFunctionLong;   // 按键长按功能对应的函数指针
} key_GPIO;                     // 按键对应信息

// 按键对应的IO口信息及功能回调函数
key_GPIO key[4] = {
    {GPIOB,GPIO_PIN_0,IDLE,0,0,function_B1},
    {GPIOB,GPIO_PIN_1,IDLE,0,0,function_B2},
    {GPIOB,GPIO_PIN_2,IDLE,0,0,function_B3},
    {GPIOA,GPIO_PIN_0,IDLE,1,0,function_B4,function_B4_long}
};

// 按键B1的功能实现
void function_B1()
{
    led_addr = led_addr | 0x03; // 更新LED位置
    led_disp(led_addr);         // 控制LED显示
}

// 按键B2的功能实现
void function_B2()
{
    led_addr = led_addr | 0x0C; // 更新LED位置
    led_disp(led_addr);         // 控制LED显示
}

// 按键B3的功能实现
void function_B3()
{
    led_addr = led_addr ^ 0x30; // 更新LED位置
    led_disp(led_addr);         // 控制LED显示
}

// 按键B4的功能实现
void function_B4()
{
    led_addr = led_addr ^ 0x40; // 更新LED位置
    led_disp(led_addr);         // 控制LED显示
}

// 按键B4长按的功能实现
void function_B4_long()
{
    led_addr = led_addr & 0x80; // 更新LED位置
    led_disp(led_addr);         // 控制LED显示
}

// 定时器中断处理函数,每10ms执行一次
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static unsigned int tim4_count = 0; // 用于TIM4计数,实现分时复用
    
    // 判断中断来源是否是TIM4
    if(htim->Instance == TIM4)
    {
        unsigned int i = 0; // 声明变量 i,用于循环计数

        // 遍历按键数组
        for(i = 0 ; i < 4 ; i++)
        {
            switch(key[i].key_state)
            {
                case IDLE:
                    // 如果按键被按下,切换到 PRESSED_READY 状态
                    if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_RESET)
                    {
                        key[i].key_state = PRESSED_READY;
                    }
                    break;
                    
                case PRESSED_READY:
                    // 如果按键仍然被按下,根据按键类型执行相应的功能
                    if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_RESET)
                    {
                        if(key[i].long_mode == 1)
                        {   // 长按键,需要计时
                            key[i].key_state = PRESSED_COUNT;
                        }
                        else
                        {   // 其他按键直接执行功能并切换状态到 PRESSED_FINISH
                            key[i].keyFunction();
                            key[i].key_state = PRESSED_FINISH;
                        }                       
                    }
                    else 
                    {
                        // 按键松开,切换回 IDLE 状态
                        key[i].key_state = IDLE;
                    }
                    break;
                    
                case PRESSED_FINISH:
                    // 检测按键是否松开,如果是则切换回 IDLE 状态
                    if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_SET)
                    {
                        key[i].key_state = IDLE;
                    }                       
                    break;
                    
                case PRESSED_COUNT:
                    // 对长按键进行计时
                    key[i].pressed_time ++;
                    if(key[i].pressed_time >= 100)
                    {   
                        // 如果长按键计时达到阈值,则执行长按功能并切换状态到 PRESSED_FINISH
                        key[i].keyFunctionLong();
                        key[i].pressed_time = 0;
                        key[i].key_state = PRESSED_FINISH;
                    }
                    else if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_SET)
                    {   
                        // 如果长按键松开,根据长按时间决定是否执行短按功能,并切换回 IDLE 状态
                        if(key[i].pressed_time <= 50)
                        {
                            key[i].keyFunction();
                        }
                        key[i].pressed_time = 0;
                        key[i].key_state = IDLE;
                    }
                    break;
                    
                default:
                    // 默认情况下切换回 IDLE 状态
                    key[i].key_state = IDLE;
                    break;
            }
        }   
        
        // 每500ms进行一次灯的移位
        tim4_count++; // TIM4计数递增    
        if(tim4_count == 50)
        {
            tim4_count = 0; // 重置计数变量,很重要
            led_addr = led_addr ^ 0x80; // 位异或操作
            led_disp(led_addr);         // LED显示函数,根据新的LED位置控制LED灯亮灭
        }
    }
}
unsigned int i = 0; // 声明变量 i,用于循环计数

// 遍历按键数组
for(i = 0 ; i < 4 ; i++)
{
		switch(key[i].key_state)
		{
				case IDLE:
						// 如果按键被按下,切换到 PRESSED_READY 状态
						if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_RESET)
						{
								key[i].key_state = PRESSED_READY;
						}
						break;
						
				case PRESSED_READY:
						// 如果按键仍然被按下,根据按键类型执行相应的功能
						if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_RESET)
						{
								if(key[i].long_mode == 1)
								{   // 长按键,需要计时
										key[i].key_state = PRESSED_COUNT;
								}
								else
								{   // 其他按键直接执行功能并切换状态到 PRESSED_FINISH
										key[i].keyFunction();
										key[i].key_state = PRESSED_FINISH;
								}                       
						}
						else 
						{
								// 按键松开,切换回 IDLE 状态
								key[i].key_state = IDLE;
						}
						break;
						
				case PRESSED_FINISH:
						// 检测按键是否松开,如果是则切换回 IDLE 状态
						if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_SET)
						{
								key[i].key_state = IDLE;
						}                       
						break;
						
				case PRESSED_COUNT:
						// 对长按键进行计时
						key[i].pressed_time ++;
						if(key[i].pressed_time >= 100)
						{   
								// 如果长按键计时达到阈值,则执行长按功能并切换状态到 PRESSED_FINISH
								key[i].keyFunctionLong();
								key[i].pressed_time = 0;
								key[i].key_state = PRESSED_FINISH;
						}
						else if(HAL_GPIO_ReadPin(key[i].port, key[i].pin) == GPIO_PIN_SET)
						{   
								// 如果长按键松开,根据长按时间决定是否执行短按功能,并切换回 IDLE 状态
								if(key[i].pressed_time <= 50)
								{
										key[i].keyFunction();
								}
								key[i].pressed_time = 0;
								key[i].key_state = IDLE;
						}
						break;
						
				default:
						// 默认情况下切换回 IDLE 状态
						key[i].key_state = IDLE;
						break;
		}

四、长短按演示效果

【蓝桥杯嵌入式】独立按键长短按

更多蓝桥杯嵌入式的精彩内容敬请期待,觉得本内容有用的话,点赞收藏加关注支持一下吧!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值