key与easy_button

key模块

输入模式下的GPIO的设置

1. 上拉 (Pull-up) ⬆️ —— 默认"开"

它是啥? 想象引脚内部有一根"橡皮筋"轻轻地把它往"高电平"(通常是电源 VCC)拉。当没有更强的力量(比如按键按下接地)作用时,它就保持在高电平。

为啥用? 防止引脚"悬空"乱飘。就像一个开关,默认是开着的,只有你按下时才关断。

何时用? 当你需要引脚在空闲时稳定地输出高电平,或者连接的按键按下时会把引脚拉到低电平 (GND)。这是最常见的按键连接方式之一。

2. 下拉 (Pull-down) ⬇️ —— 默认"关"

它是啥? 和上拉相反,这次"橡皮筋"是把引脚往"低电平"(地线 GND)拉。默认状态是低电平。

为啥用? 同样是为了防止悬空,提供一个明确的默认状态。

何时用? 当你需要引脚空闲时稳定在低电平,或者连接的按键按下时会把引脚接到高电平 (VCC)。相对上拉来说,用得稍微少一些。

3. 浮空 (Floating / High-Impedance) —— “随波逐流”

它是啥? 引脚内部既不上拉也不下拉,像个与世无争的"隐士"。它的电平完全由外部连接决定。如果什么都没接,它的状态就像空气中的羽毛,极其不稳定,容易受到各种电磁干扰。

为啥用? 有些特定场景需要引脚呈现高阻态,不影响外部电路(比如模拟信号输入,或者需要外部电路精确控制电平)。但在简单的数字输入,尤其是按键检测中,极少使用浮空模式,因为它太容易"受惊"了。

注意! 直接用浮空模式连接按键通常是不可靠的,你需要外部电路(比如外部上拉或下拉电阻)来明确默认电平。

在key中需要查看原理图后选择GPIO的上拉或者下拉电阻(本文选择下拉电阻)


HAL库的HAL_GPIO_Readpin()解读

它可以检测GPIO的引脚状态

GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);

HAL_GPIO_Readpin的返回值有两个(通过枚举)

  • GPIO_PIN_SET
  • GPIO_PIM_RESET

可以直接对HAL_GPIO_Readpin()直接判断使用


移植btn库(基于easy_button库 单按键)

  1. const ebtn_btn_param_t defaul_ebtn_param = EBTN_PARAMS_INIT的解读
const ebtn_btn_param_t defaul_ebtn_param = EBTN_PARAMS_INIT(
    20,     // time_debounce: 按下稳定 20ms
    20,     // time_debounce_release: 释放稳定 20ms
    50,     // time_click_pressed_min: 最短单击按下 50ms
    500,    // time_click_pressed_max: 最长单击按下 500ms (超过则不算单击)
    300,    // time_click_multi_max: 多次单击最大间隔 300ms (两次点击间隔超过则重新计数)
    500,    // time_keepalive_period: 长按事件周期 500ms (按下超过 500ms 后,每 500ms 触发一次)
    5       // max_consecutive: 最多支持 5 连击
);

  • EBTN_PARAMS_INIT的解读
#define EBTN_PARAMS_INIT(_time_debounce, _time_debounce_release, _time_click_pressed_min, _time_click_pressed_max, _time_click_multi_max,                      \
                         _time_keepalive_period, _max_consecutive)                                                                                             \
    {                                                                                                                                                          \
        .time_debounce = _time_debounce, .time_debounce_release = _time_debounce_release, .time_click_pressed_min = _time_click_pressed_min,                   \
        .time_click_pressed_max = _time_click_pressed_max, .time_click_multi_max = _time_click_multi_max, .time_keepalive_period = _time_keepalive_period,     \
        .max_consecutive = _max_consecutive                                                                                                                    \
    }

这段代码的用处是给 defaul_ebtn_param的结构体赋值正好与ebtn_btn_param_t定义的类型对应

  • defaul_ebtn_param的解读

defaul_ebtn_param为被定义的变量它用于保存这些参数然后给用户的key

  • ebtn_btn_param_t的解读

它是用来定义按键参数的结构体

  1. 解读下列函数
/* 2. 定义静态按键列表 */
// 宏: EBTN_BUTTON_INIT(按键ID, 参数指针)
ebtn_btn_t static_buttons[] = {
    EBTN_BUTTON_INIT(1, &key_param_normal), // KEY1, ID=1, 使用 'key_param_normal' 参数
    EBTN_BUTTON_INIT(2, &key_param_normal), // KEY2, ID=2, 也使用 'key_param_normal' 参数
};
  • ebtn_btn_t是结构体主要功能是KEY_ID
typedef struct ebtn_btn
{
    uint16_t key_id;    /*!< User defined custom argument for callback function purpose */
    uint8_t flags;      /*!< Private button flags management */
    uint8_t event_mask; /*!< Private button event mask management */

    ebtn_time_t time_change;       /*!< Time in ms when button state got changed last time after valid
                                   debounce */
    ebtn_time_t time_state_change; /*!< Time in ms when button state got changed last time */

    ebtn_time_t keepalive_last_time; /*!< Time in ms of last send keep alive event */
    ebtn_time_t click_last_time;     /*!< Time in ms of last successfully detected (not sent!) click event
                                      */

    uint16_t keepalive_cnt; /*!< Number of keep alive events sent after successful on-press
                            detection. Value is reset after on-release */
    uint16_t click_cnt;     /*!< Number of consecutive clicks detected, respecting maximum timeout
                        between clicks */

    const ebtn_btn_param_t *param;
} ebtn_btn_t;
  • static_buttons是被定义的结构体数组
  • EBTN_BUTTON_INIT是将KEY_ID绑定参数的初始化宏定义
  1. /* 1. 实现获取按键状态的回调函数 */
    // 函数原型: uint8_t (*ebtn_get_state_fn)(struct ebtn_btn *btn);
    uint8_t my_get_key_state(struct ebtn_btn *btn) {
        // 根据传入的按钮实例中的 key_id 判断是哪个物理按键
        switch (btn->key_id) {
            case 1: // 请求读取 KEY1 的状态
                // 假设 KEY1 接在 PB0,按下为低电平 (返回 1 代表按下)
                return (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET);
            case 2: // 请求读取 KEY2 的状态
                // 假设 KEY2 接在 PB1,按下为低电平
                return (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET);
            // ... 可以添加更多按键的读取逻辑 ...
            default:
                // 对于库内部处理组合键等情况,或者未知的 key_id,安全起见返回 0 (未按下)
                return 0;
        }
        // 注意:返回值 1 表示 "活动/按下",0 表示 "非活动/释放"
    }
    
  • my_get_key_state的原型是typedef uint8_t (*ebtn_get_state_fn)(struct ebtn_btn *btn) 定义了一个函数指针类型。在int ebtn_init(ebtn_btn_t *btns, uint16_t btns_cnt, ebtn_btn_combo_t *btns_combo, uint16_t btns_combo_cnt, ebtn_get_state_fn get_state_fn, ebtn_evt_fn evt_fn);中被调用。
  • 回调函数用于告诉ebtn库key的引脚状态
  • ebtn_btn和上述的``ebtn_btn_t`一样的结构体参数(key_id 等等)。
  • switch (btn->key_id)这段代码将硬件和KEY_ID联系起来,每一个case都会返回给函数0或者1.
/* 2. 实现处理按键事件的回调函数 */
// 函数原型: void (*ebtn_evt_fn)(struct ebtn_btn *btn, ebtn_evt_t evt);
void my_handle_key_event(struct ebtn_btn *btn, ebtn_evt_t evt) {
    uint16_t key_id = btn->key_id;                 // 获取触发事件的按键 ID
    uint16_t click_cnt = ebtn_click_get_count(btn); // 获取连击次数 (仅在 ONCLICK 事件时有意义)
    // uint16_t kalive_cnt = ebtn_keepalive_get_count(btn); // 获取长按计数 (仅在 KEEPALIVE 事件时有意义)

    // 调试打印 (可选)
    // printf("Key ID: %d, Event: %d", key_id, evt);

    // 根据事件类型进行处理
    switch (evt) {
        case EBTN_EVT_ONPRESS: // 按下事件 (消抖成功后触发一次)
            // printf(" - Pressed\n");
            // 可以在这里处理按下即触发的操作,比如点亮提示灯
            if (key_id == 1) { /* Do something for KEY1 press */ }
            break;

        case EBTN_EVT_ONRELEASE: // 释放事件 (消抖成功后触发一次)
            // printf(" - Released\n");
            // 可以在这里处理释放时触发的操作
             if (key_id == 1) { /* Do something for KEY1 release */ }
            break;

        case EBTN_EVT_ONCLICK: // 单击/连击事件 (在释放后,或达到最大连击数,或超时后触发)
            // printf(" - Clicked (%d times)\n", click_cnt);
            // --- 根据 key_id 和 click_cnt 执行不同操作 ---
            if (key_id == 1) { // 如果是 KEY1 触发的 CLICK
                if (click_cnt == 1) {
                    // KEY1 单击
                    // printf("  Action: KEY1 Single Click - Toggle LED1\n");
                    // HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
                } else if (click_cnt == 2) {
                    // KEY1 双击
                    // printf("  Action: KEY1 Double Click - Switch Mode\n");
                }
                // ... 可以继续判断 3击, 4击 ...
            } else if (key_id == 2) { // 如果是 KEY2 触发的 CLICK
                 if (click_cnt == 1) {
                    // KEY2 单击
                    // printf("  Action: KEY2 Single Click - Toggle LED2\n");
                 }
            } else if (key_id == 101) { // 如果是组合键 (KEY1+KEY2) 触发的 CLICK
                if (click_cnt == 1) {
                     // 组合键单击
                     // printf("  Action: Combo Key 101 Single Click - Reset System\n");
                }
            }
            break;

        case EBTN_EVT_KEEPALIVE: // 保持活动/长按事件 (按下持续时间超过阈值后,按周期触发)
            // printf(" - Keep Alive (Long Press, Count: %d)\n", kalive_cnt);
            if (key_id == 1) {
                // KEY1 长按
                // printf("  Action: KEY1 Long Press - Increase Volume\n");
            }
            break;

        default: // 未知事件 (理论上不应发生)
            // printf(" - Unknown Event\n");
            break;
    }
}
  • 与上面的获取按键状态的函数一样
  • ebtn_evt_t是一个结构体他的代码如下
typedef enum
{
    EBTN_EVT_ONPRESS = 0x00, /*!< On press event - sent when valid press is detected */
    EBTN_EVT_ONRELEASE,      /*!< On release event - sent when valid release event is detected (from
                                active to inactive) */
    EBTN_EVT_ONCLICK,        /*!< On Click event - sent when valid sequence of on-press and on-release
                                events occurs */
    EBTN_EVT_KEEPALIVE,      /*!< Keep alive event - sent periodically when button is active */
} ebtn_evt_t;

参数用于判断事件为什么(按下,松开,连击,持续按压)

EBTN_EVT_ONPRESS:表示按钮被按下的事件。

EBTN_EVT_ONRELEASE:表示按钮被释放的事件(从按下状态恢复到未按下状态)。

EBTN_EVT_ONCLICK:表示一次完整的点击事件(通常是按下后释放的组合)。

EBTN_EVT_KEEPALIVE:表示按钮在按下状态下周期性发送的“保持活跃”事件。

  • uint16_t click_cnt = ebtn_click_get_count(btn); // 获取连击次数 (仅在 ONCLICK 事件时有意义)
void app_ebtn_init(void)
{
	ebtn_init(btns, EBTN_ARRAY_SIZE(btns), NULL, 0, prv_btn_get_state, prv_btn_event);
}
  • ebtn_init(btns, EBTN_ARRAY_SIZE(btns), NULL, 0, prv_btn_get_state, prv_btn_event);初始化ebtn库,上面的参数设置。

  • int ebtn_init(ebtn_btn_t *btns, uint16_t btns_cnt, ebtn_btn_combo_t *btns_combo, uint16_t btns_combo_cnt, ebtn_get_state_fn get_state_fn, ebtn_evt_fn evt_fn);是它的原型函数。

  • ebtn_btn_t *btns: 指向按钮配置数组,定义需要管理的按钮。

    uint16_t btns_cnt: 按钮数组的长度(按钮数量)。

    ebtn_btn_combo_t *btns_combo: 指向组合键配置数组,定义支持的按钮组合。

    uint16_t btns_combo_cnt: 组合键数组的长度(组合键数量)。

    ebtn_get_state_fn get_state_fn: 函数指针,指定如何获取按钮状态(例如读取 GPIO)。

    ebtn_evt_fn evt_fn: 函数指针,指定如何处理按钮事件(例如按下、释放、点击)。

void btn_task(void)
{
	ebtn_process(HAL_GetTick());
}

    while (1) {
        // *** 在主循环中调用 ebtn 处理函数 ***
        // 注意:需要确保主循环执行频率足够快且稳定,否则会影响按键检测精度
        ebtn_process(HAL_GetTick());

        // ... 其他主循环任务 ...
        // 比如检查事件回调函数设置的标志位并执行相应操作
    }
  • ebtn 库内部的所有计时(去抖、单击超时、长按周期)都依赖于你通过 ebtn_process 传入的当前时间。如果调用间隔不规律或太长,会导致时间判断错误,按键事件也就无法被正确识别了。

组合按键

注意:组合键的配置必须在 ebtn_init 成功调用之后进行,因为它需要库内部已经知道了所有普通按键及其内部索引

/* 3. 定义静态组合按键列表 (可选) */
// 宏: EBTN_BUTTON_COMBO_INIT(按键ID, 参数指针)
ebtn_btn_combo_t static_combos[] = {
    // 假设 KEY1+KEY2 组合键
    EBTN_BUTTON_COMBO_INIT(101, &key_param_normal), // 组合键, ID=101 (必须与普通按键ID不同)
};
  • 与上面定义的单个按键类似,注意名不一样,与上文单个结构体搭配构成组合按键 ,下面是原函数

  • typedef struct ebtn_btn_combo
    {
        BIT_ARRAY_DEFINE(comb_key, EBTN_MAX_KEYNUM); /*!< select key index - `1` means active, `0` means inactive */
    
        ebtn_btn_t btn;//通过单个按键进行组合
    } ebtn_btn_combo_t;
    
  • EBTN_BUTTON_COMBO_INIT(101, &key_param_normal)// 组合键, ID=101 (必须与普通按键ID不同)

  1. 当你选择key1和key2进行组合按键时,这段代码要在ebtn初始化后紧随进行
    int key1_index = ebtn_get_btn_index_by_key_id(1); // 获取 KEY1 (ID=1) 的内部索引
    int key2_index = ebtn_get_btn_index_by_key_id(2); // 获取 KEY2 (ID=2) 的内部索引
  1. 将你选择的组合按键放进同一个数组里进行绑定
        ebtn_combo_btn_add_btn_by_idx(&static_combos[0], key1_index); // 将 KEY1 添加到组合键
        ebtn_combo_btn_add_btn_by_idx(&static_combos[0], key2_index); // 将 KEY2 添加到组合键
  1. 在下面的回调函数中用组合的key_id(上文的101)进行注册事件
void my_handle_key_event(struct ebtn_btn *btn, ebtn_evt_t evt) 
{
    uint16_t key_id = btn->key_id;
    if(key_id==101{
        ''''//触发事件
        
    }
    
}

组合按键练习

//按键0+按键1 复制ucled
//按键0+按键2 粘贴ucled
//按键0+按键3 剪切ucled(复制完后全部熄灭)

  1. 先进行注册id为组合按键做准备
ebtn_btn_combo_t static_combos[] = {
	
    // 假设 KEY1+KEY2 组合键
    EBTN_BUTTON_COMBO_INIT(101, &key_param_normal), // 组合键, ID=101 (必须与普通按键ID不同)
    EBTN_BUTTON_COMBO_INIT(102, &key_param_normal),
    EBTN_BUTTON_COMBO_INIT(103, &key_param_normal),	
	
};
  1. 获取单个按键的索引值,为后续的按键组合做准备
	int key1_index = ebtn_get_btn_index_by_key_id(1); // 获取 KEY1 (ID=1) 的内部索引
    int key2_index = ebtn_get_btn_index_by_key_id(2); // 获取 KEY2 (ID=2) 的内部索引
    int key3_index = ebtn_get_btn_index_by_key_id(3);
    int key4_index = ebtn_get_btn_index_by_key_id(4);	
  1. 将第二步获取的按键索引值放进数组的同一位置 成组合按键,组合按键的key_id与上面定义的key_id顺序对应
			//复制ucled
   ebtn_combo_btn_add_btn_by_idx(&static_combos[0], key1_index); // 将 KEY1 添加到组合键
   ebtn_combo_btn_add_btn_by_idx(&static_combos[0], key2_index); // 将 KEY2 添加到组合键		
			//粘贴led
   ebtn_combo_btn_add_btn_by_idx(&static_combos[1], key1_index); // 将 KEY1 添加到组合键
   ebtn_combo_btn_add_btn_by_idx(&static_combos[1], key3_index); // 将 KEY2 添加到组合键	
			//剪切led
   ebtn_combo_btn_add_btn_by_idx(&static_combos[2], key1_index); // 将 KEY1 添加到组合键
   ebtn_combo_btn_add_btn_by_idx(&static_combos[2], key4_index); // 将 KEY2 添加到组合键	
  1. 对led的复制,粘贴,剪切的操作
			
	if(evt==EBTN_EVT_ONCLICK){
		switch(key_id)
			{
				case 101://ucled复制
					memcpy(copy_ucLed, ucLed, sizeof(ucLed) );
//					for(int i=0;i<6;i++)
//					{
//						copy_ucLed[i]=ucLed[i];
//					}
				 break;
				case 102://ucled粘贴
					memcpy(ucLed, copy_ucLed, sizeof(copy_ucLed) );
//					for(int i=0;i<6;i++)
//					{
//						ucLed[i]=copy_ucLed[i];
//					}
				 break;			
				case 103://ucled剪切
					memcpy(copy_ucLed, ucLed, sizeof(ucLed) );					
					memcpy(ucLed, clear_ucLed, sizeof(copy_ucLed) );				
//					for(int i=0;i<6;i++)
//					{
//						copy_ucLed[i]=ucLed[i];
//					}
//					for(int i=0;i<6;i++)
//					{
//						ucLed[i]=clear_ucLed[i];
//					}			
				 break;	
        	}
			}

你可能感兴趣的:(单片机,嵌入式硬件)