BUTTON指南

本文说明该组件的功能、关键源码和应用接入方式。

1. 参考代码

文件

作用

solution\components\button\button_srv_custom.c

配置项目按键事件和组合键策略

solution\framework\service\button\button_service.c

扫描按键状态并向应用分发按键事件

2. 介绍

Button驱动包括两层:通用定时器(GPT)的硬件访问层(HAL)和RT-Thread的适配层。 按钮库使用引脚设备来检测各种按钮动作。 它使用一个计时器来处理去抖动,而另一个计时器来检测长按动作。

它支持以下操作

  • 按下:按钮被按下

  • 释放:按钮被释放

  • 长按:按钮被按下并保持由 BUTTON_ADV_ACTION_CHECK_DELAY 指定的时间

  • 点击:按钮被按下和释放,中间不会触发长按动作

3. BUTTON配置

3.1 PINMUX引脚配置

需要将相应的key引脚配置为IO模式。

    HAL_PIN_Set(PAD_PA77, GPIO_A77, PIN_NOPULL, 1);              //key3
    HAL_PIN_Set(PAD_PA78, GPIO_A78, PIN_NOPULL, 1);              //key4
    HAL_PIN_Set(PAD_PA79, GPIO_A79, PIN_NOPULL, 1);              //key5
    HAL_PIN_Set(PAD_PA80, GPIO_A80, PIN_NOPULL, 1);              //key6

4. BUTTON功能代码流程

相关文件如下:

  1. button驱动文件 button_service.c

  2. button动作执行文件 button_srv_custom.c 代码整体流程为: init_pin() --> button_service_callback() --> button_event_handler() --> button_keypad_cb()

接口实现如下:

int32_t button_keypad_cb(lv_key_t key, lv_indev_state_t event)
{
    if ((LV_INDEV_STATE_PR == event) && (LV_KEY_HOME == key))
    {
        LOG_I("keypad pressed HOME");
#ifdef BSP_USING_PC_SIMULATOR
        /* For simulator, don't call the wakeup procedure. Therefor, here, if aod running, wake up now. */
        if (app_gui_aod_wakeup()) return 0;
#endif
        /* if ota running, wake up now. */
        if (app_ota_status_get()) return 0;

#ifdef POWER_ON_GUIDE
        /* if power_on_guide is running, ignore it. */
        if (power_on_guide_active()) return 0;
#endif
#ifdef APP_POPUP_USED
        /* refer to apple's watch solution */
        popup_destroy_all();
#endif
#ifdef GUI_APP_FRAMEWORK
        /* switch applicaiton between Main and Clock. Customer will  modify it according to requirement. */
        if (gui_app_is_actived("Main"))
        {
#if defined(HOME_DOUBLE_CLICK_FUNCTION)
            mainmenu_db_func_entry();
#else
            gui_app_run("Tileview");
#endif
        }
        else
        {
            gui_app_run("Main");
#if defined(GUI_APP_FRAMEWORK)&& !defined (APP_TRANS_ANIMATION_NONE)
            lvsf_gesture_bars_realign();
#endif
        }
#endif
    }
    else if ((LV_INDEV_STATE_PR == event) && (LV_KEY_ESC == key))
    {
        LOG_I("keypad pressed ESC");
#ifdef GUI_APP_FRAMEWORK
        gui_app_goback();
#endif
    }
    else if ((LV_INDEV_STATE_PR == event) && (LV_KEY_UP == key))
    {
        LOG_I("keypad pressed UP");
    }
        else if ((LV_INDEV_STATE_PR == event) && (LV_KEY_DOWN == key))
    {
        LOG_I("keypad pressed DOWN");
    }

    return LV_BLOCK_EVENT;
}

5. BUTTON使用流程

button采用订阅sub pin_service服务方式,button_service_callback实施监听订阅的按键消息,读取订阅按键消息来执行相应的动作。

  1. button初始化

static int init_pin(void)
{
    button_active_state_t active_state;
    //key1 配置
#if (SWITCH_ON_PIN < GPIO1_PIN_NUM)
    /* SWITCH_ON PIN is by HPSYS side */
#if defined (BSP_KEY1_ACTIVE_HIGH) && (BSP_KEY1_ACTIVE_HIGH == 1)
    active_state = BUTTON_ACTIVE_HIGH;
#else
    active_state = BUTTON_ACTIVE_LOW;
#endif
    button_config(SWITCH_ON_PIN, active_state, "btn0"); //初始化key1
#endif /* SWITCH_ON_PIN < GPIO1_PIN_NUM */

    gui_ctx_init();
    button_key1_handle = datac_open(); //获取订阅消息
    RT_ASSERT(DATA_CLIENT_INVALID_HANDLE != button_key1_handle);
    datac_subscribe(button_key1_handle, "btn0", button_service_callback, BSP_KEY1_PIN); //配置消息订阅的回调函数
    button_keypad_map_set(BSP_KEY1_PIN, LV_KEY_HOME);//配置该按键的keypad功能,key1 -- HOME

#ifdef BSP_KEY2_PIN
#if defined (BSP_KEY2_ACTIVE_HIGH) && (BSP_KEY2_ACTIVE_HIGH == 1)
    active_state = BUTTON_ACTIVE_HIGH;
#else
    active_state = BUTTON_ACTIVE_LOW;
#endif
    button_config(BSP_KEY2_PIN, active_state, "btn1");
    button_key2_handle = datac_open();
    RT_ASSERT(DATA_CLIENT_INVALID_HANDLE != button_key2_handle);
    datac_subscribe(button_key2_handle, "btn1", button_service_callback, BSP_KEY2_PIN);
    button_keypad_map_set(BSP_KEY2_PIN, LV_KEY_UP);
#endif

    rt_pm_request(PM_SLEEP_MODE_IDLE);
    sys_poweron_mng_init();
    rt_pm_release(PM_SLEEP_MODE_IDLE);
    return 0;
}
  1. BUTTON回调函数

/**
 * @brief  Button service callback function of btn data service
 * @param  arg Data service callback argment
 */
static int button_service_callback(data_callback_arg_t *arg)
{
    LOG_I("button_service_callback: msg_id 0x%x", arg->msg_id);
    if (MSG_SERVICE_DATA_NTF_IND == arg->msg_id)//判断是否是NTF的消息ID
    {
        button_action_t action;
        RT_ASSERT(arg->data_len == sizeof(action));
        action = *(button_action_t *)arg->data;
        if (PM_HIBERNATE_BOOT != SystemPowerOnModeGet())
            button_long_press_decode(action);//长按按键定时器
#if defined (BF0_HCPU) && defined (USING_BUTTON_LIB) && !defined (BSP_USING_PC_SIMULATOR)
        button_event_handler(action, arg->user_data);//按键服务函数
#endif
    }
    return 0;
}
  1. 按键服务函数

/**
 * @brief  bButton event handle function
 * @param  action Button action status
 * @param  key_pin pin of key.
 */
static void button_event_handler(button_action_t action, uint32_t key_pin)
{
    LOG_I("button:%d, %d", gui_is_active(), action);

    if (RT_ERROR == button_event_cb(action))  //defined in button_srv_custome.c. user can self_defined.
        return;

#ifdef BSP_USING_PM
    if (gui_is_invalid())
    {
        if (PM_HIBERNATE_BOOT == SystemPowerOnModeGet())
        {
            LOG_I("button_handler(invalid 1 ): action %d sys_get_is_power_on %d", action, sys_get_is_power_on());
            sys_pwron_evt_t evt;
            gui_pm_action_t pm_action;
            if (!sys_get_is_power_on())
            {
                evt = SYS_PWRON_EVT_INVALID;
                switch (action)
                {
                case BUTTON_RELEASED:
                {
                    evt = SYS_PWRON_EVT_BUTTON_RELEASED;
                    break;
                }
                case BUTTON_LONG_PRESSED:
                {
                    evt = SYS_PWRON_EVT_BUTTON_LONG_PRESSED;
                    break;
                }
                default:
                {
                    pm_action = GUI_PM_ACTION_INVALID;
                }
                }
                if (GUI_PM_ACTION_INVALID != evt)
                {
                    sys_poweron_fsm(evt);
                }
            }
        }
        else
            LOG_I("The GUI is not initialized, please try again later");
        return;
    }

    if (!gui_is_active())
    {
        /* button event handler in UI active state */

        gui_pm_action_t pm_action;
        sys_pwron_evt_t evt;

        LOG_I("button_handler(inactive 2 ): action %d sys_get_is_power_on %d", action, sys_get_is_power_on());

        if (!sys_get_is_power_on())
        {
            evt = SYS_PWRON_EVT_INVALID;
            switch (action)
            {
            case BUTTON_RELEASED:
            {
                evt = SYS_PWRON_EVT_BUTTON_RELEASED;
                break;
            }
            case BUTTON_LONG_PRESSED:
            {
                evt = SYS_PWRON_EVT_BUTTON_LONG_PRESSED;
                break;
            }
            default:
            {
                pm_action = GUI_PM_ACTION_INVALID;
            }
            }
            if (GUI_PM_ACTION_INVALID != evt)
            {
                sys_poweron_fsm(evt);
            }
        }
        else
        {
            pm_action = GUI_PM_ACTION_INVALID;
            switch (action)
            {
            case BUTTON_PRESSED:
            {
                pm_action = GUI_PM_ACTION_BUTTON_PRESSED;
                break;
            }
            case BUTTON_RELEASED:
            {
                pm_action = GUI_PM_ACTION_BUTTON_RELEASED;
                break;
            }
            case BUTTON_CLICKED:
            {
                pm_action = GUI_PM_ACTION_BUTTON_CLICKED;
                break;
            }
            case BUTTON_LONG_PRESSED:
            {
                pm_action = GUI_PM_ACTION_BUTTON_LONG_PRESSED;
                break;
            }
            default:
            {
                pm_action = GUI_PM_ACTION_INVALID;
            }
            }

            if (GUI_PM_ACTION_INVALID != pm_action)
            {
                app_gui_wakeup(pm_action);
            }
        }
    }
    else //gui_is_active()
#endif /* BSP_USING_PM */
    {
        /* button event handler in UI inactive state */

        LOG_I("button_handler(active 3 ): action %d", action);

        lv_disp_trig_activity(NULL);

        switch (action)
        {
        case BUTTON_CLICKED:
        {
            button_keypad_status.last_key = button_keypad_to_ascii(key_pin);
            button_keypad_status.last_key_state = KEYPAD_KEY_STATE_PRESSED;
            break;
        }
        case BUTTON_LONG_PRESSED:
        {
#ifdef BSP_USING_PM
            gui_pm_fsm(GUI_PM_ACTION_BUTTON_LONG_PRESSED);
#endif
            break;
        }
        default:
            ;
        }
    }
}
  1. 按键功能函数

int32_t button_keypad_cb(lv_key_t key, lv_indev_state_t event)
{
    if ((LV_INDEV_STATE_PR == event) && (LV_KEY_HOME == key))//判断是否是 LV_KEY_HOME 按键按下
    {
        LOG_I("keypad pressed HOME");
#ifdef BSP_USING_PC_SIMULATOR
        /* For simulator, don't call the wakeup procedure. Therefor, here, if aod running, wake up now. */
        if (app_gui_aod_wakeup()) return 0;
#endif
        /* if ota running, wake up now. */
        if (app_ota_status_get()) return 0;

#ifdef POWER_ON_GUIDE
        /* if power_on_guide is running, ignore it. */
        if (power_on_guide_active()) return 0;
#endif
#ifdef APP_POPUP_USED
        /* refer to apple's watch solution */
        popup_destroy_all();
#endif
#ifdef GUI_APP_FRAMEWORK
        /* switch applicaiton between Main and Clock. Customer will  modify it according to requirement. */
        if (gui_app_is_actived("Main"))
        {
#if defined(HOME_DOUBLE_CLICK_FUNCTION)
            mainmenu_db_func_entry();
#else
            gui_app_run("Tileview");
#endif
        }
        else
        {
            gui_app_run("Main");
#if defined(GUI_APP_FRAMEWORK)&& !defined (APP_TRANS_ANIMATION_NONE)
            lvsf_gesture_bars_realign();
#endif
        }
#endif
    }
    else if ((LV_INDEV_STATE_PR == event) && (LV_KEY_ESC == key))
    {
        LOG_I("keypad pressed ESC");
#ifdef GUI_APP_FRAMEWORK
        gui_app_goback();
#endif
    }
#ifdef _MSC_VER
    else if ((LV_INDEV_STATE_PR == event) && (LV_KEY_NEXT == key))
    {
    }
#endif
    else if ((LV_INDEV_STATE_PR == event) && (LV_KEY_UP == key))//判断是否是 LV_KEY_UP 按键按下
    {
        LOG_I("keypad pressed UP");
    }
        else if ((LV_INDEV_STATE_PR == event) && (LV_KEY_DOWN == key))//判断是否是 LV_KEY_DOWN 按键按下
    {
        LOG_I("keypad pressed DOWN");
    }

    return LV_BLOCK_EVENT;
}

6. BUTTON 扩展

当前公共按键配置定义在 sdk/customer/peripherals/Kconfig 的 Key Config 菜单中,源码默认提供 BSP_USING_KEY1 和 BSP_USING_KEY2 两组配置;button_service.c 中的初始化流程也按 BSP_KEY1_PIN、BSP_KEY2_PIN 注册按键服务。

如需增加更多实体按键,不能只在文档或工程配置中新增一个宏;需要同步扩展 Kconfig 配置、按键句柄、button_config()、datac_subscribe() 以及 button_keypad_map_set() 的初始化逻辑,或使用现有 ADC 按键组能力扩展多个键值。

配置按键数量可参考 menuconfig 中的 max button number:

../_images/button_4.png

按键功能处理在 button_keypad_cb() 中增加对应 lv_key_t 分支,例如:

    else if ((LV_INDEV_STATE_PRESSED == event) && (LV_KEY_DOWN == key))
    {
        LOG_I("keypad pressed DOWN");
    }

7. ADC 按键配置说明

7.1 配置总览

ADC 按键配置在 menuconfig 中完成,主要涉及以下路径:

RT-Thread Components  →  Peripheral Drivers  →  BUTTON

7.2 基础配置步骤

  1. 开启按键功能

    [*] Enable BUTTON
    
  2. 配置通用参数

    (8) max button number                    # 最大按键数量(包含GPIO按键和ADC按键)
    (3000) detection time for advanced action in millisecond  # 长按判定时间
    [*] Register Button as Data Service      # 注册按键事件服务
    (5) max number of supported clients      # 数据服务最大客户端数
    
  3. 开启 ADC 按键

    [*] Enable ADC Buttons
    

7.3 ADC 按键组配置示例

以配置两组 ADC 按键为例:

组 1 配置(Use key1)

Number of ADC Button Groups: 2

--- ADC Button Group 1 ---
(bat1) ADC Device Name                    # ADC 设备名称 adc注册的device固定为bat1
(1) Number of Buttons in Group 1          # 按键数量
(5) ADC Device Channel                    # ADC 通道号
(3200) ADC Group1 Button1 Voltage         # 按键1目标电压 3.2V
(100) ADC Group1 Button1 Voltage Valid Range  # 偏差范围 ±100mV
(2500) ADC Group1 Button2 Voltage         # 按键2目标电压 2.5V
(100) ADC Group1 Button2 Voltage Valid Range  # 偏差范围 ±100mV

组 2 配置(Use key2)

--- ADC Button Group 2 ---
(adc0) ADC Group 2 ADC Device Name        # ADC 设备名称
(2) Number of Buttons in Group 2          # 按键数量
(1) ADC Group 2 ADC Device Channel        # ADC 通道号
(3200) ADC Group2 Button1 Voltage          # 按键1目标电压 3.2V
(80) ADC Group2 Button1 Voltage Valid Range   # 偏差范围 ±80mV
(2190) ADC Group2 Button2 Voltage         # 按键2目标电压 2.19V
(80) ADC Group2 Button2 Voltage Valid Range   # 偏差范围 ±80mV

7.4 配置参数说明

参数

说明

建议值范围

Number of ADC Button Groups

ADC 按键组数量,当前最多支持 2 组

1~2

ADC Device Name

ADC 设备名称,需与 BSP 注册名称一致

代码统一注册的为bat1

Number of Buttons in Group

该组的按键数量

根据实际硬件确定

ADC Device Channel

ADC 通道号

0~7(取决于芯片)

Button Voltage

按键目标电压(mV)

2100~3300

Voltage Valid Range

电压偏差容差(mV)

50~200

7.5 配置检查清单

完成配置后,请确认:

  • ADC 设备名称与 rt_hw_adc_register() 注册的名称一致

  • 同一组内所有按键电压区间 不重叠

  • 电压区间满足:目标电压 ± 有效范围

  • ADC 通道未被其它功能(如触摸、传感器)占用

  • GPIO 引脚已在 HAL_PIN_Set() 中正确配置

  • 按键总数未超过 max button number

7.6 常见配置错误

错误现象

可能原因

解决方法

按下按键无反应

ADC 设备名称配置错误

检查 ADC Device Name 是否匹配

按键识别错误

电压区间重叠或范围过小

增大有效范围或调整目标电压间隔

编译报错

按键组数量超过 2

修改 Number of ADC Button Groups 为 1 或 2

采样值异常

ADC 通道配置错误或被占用

确认通道号正确且未被复用

长按不触发

detection time 配置过大

检查长按判定时间配置值


8. 常见报错

TBD…