一、前言
上一章简单介绍了一下BES代码框架的组成,本章主要讲解一下BES按键模块,包含驱动、按键响应以及对应的消息处理。
二、按键驱动
1、按键的GPIO配置:
const struct HAL_KEY_GPIOKEY_CFG_T cfg_hw_gpio_key_cfg[CFG_HW_GPIOKEY_NUM] = {
#if (CFG_HW_GPIOKEY_NUM > 0)
#ifdef BES_AUDIO_DEV_Main_Board_9v0
{HAL_KEY_CODE_FN1,{HAL_IOMUX_PIN_P0_0, HAL_IOMUX_FUNC_AS_GPIO,HAL_IOMUX_PIN_VOLTAGE_VIO, HAL_IOMUX_PIN_PULLUP_ENABLE}},
{HAL_KEY_CODE_FN2,{HAL_IOMUX_PIN_P0_1, HAL_IOMUX_FUNC_AS_GPIO,HAL_IOMUX_PIN_VOLTAGE_VIO, HAL_IOMUX_PIN_PULLUP_ENABLE}},
#else
{HAL_KEY_CODE_FN1,{HAL_IOMUX_PIN_P1_3, HAL_IOMUX_FUNC_AS_GPIO,HAL_IOMUX_PIN_VOLTAGE_VIO, HAL_IOMUX_PIN_PULLUP_ENABLE}},
//{HAL_KEY_CODE_FN2,{HAL_IOMUX_PIN_P0_5, HAL_IOMUX_FUNC_AS_GPIO,HAL_IOMUX_PIN_VOLTAGE_VIO, HAL_IOMUX_PIN_PULLUP_ENABLE}},
{HAL_KEY_CODE_FN2,{HAL_IOMUX_PIN_P1_2, HAL_IOMUX_FUNC_AS_GPIO,HAL_IOMUX_PIN_VOLTAGE_VIO, HAL_IOMUX_PIN_PULLUP_ENABLE}},
#endif
#endif
};
2、按键驱动的相关函数
int app_key_open(int checkPwrKey)
开启按键功能,初始化按键线程、申请按键链表资源。
int hal_key_open(int checkPwrKey, int (* cb)(uint32_t, uint8_t))
按键驱动初始化,启动包含GPIO按键、ADC按键、拨码按键等。主要功能是初始化按键。
static void hal_key_boot_handler(void *param)
static void hal_key_debounce_handler(void *param)
上面两个函数均为键值生成函数,其中hal_key_boot_handler为启动过程中电源键的键值生成函数,仅仅在启动过程中使用,hal_key_debounce_handler为普通按键的键值生成函数,该函数功能包含了按键的防抖、键值的转化等,有兴趣可以深入研究下其中的流程,还是有点东西的。
static int send_key_event(enum HAL_KEY_CODE_T code, enum HAL_KEY_EVENT_T event)
按键事件上报。
3、按键参数的配置
下面的主要是按键长按、短按、抖动时间,以及多击的间隔时间等配置,可以根据实际的项目要求及形态进行配置。
#ifndef CFG_SW_KEY_LLPRESS_THRESH_MS
#define CFG_SW_KEY_LLPRESS_THRESH_MS 5000
#endif
#ifndef CFG_SW_KEY_LPRESS_THRESH_MS
#define CFG_SW_KEY_LPRESS_THRESH_MS 1500
#endif
#ifndef CFG_SW_KEY_REPEAT_THRESH_MS
#define CFG_SW_KEY_REPEAT_THRESH_MS 500
#endif
#ifndef CFG_SW_KEY_DBLCLICK_THRESH_MS
#define CFG_SW_KEY_DBLCLICK_THRESH_MS 400
#endif
#ifndef CFG_SW_KEY_INIT_DOWN_THRESH_MS
#define CFG_SW_KEY_INIT_DOWN_THRESH_MS 200
#endif
#ifndef CFG_SW_KEY_INIT_LPRESS_THRESH_MS
#define CFG_SW_KEY_INIT_LPRESS_THRESH_MS 3000
#endif
#ifndef CFG_SW_KEY_INIT_LLPRESS_THRESH_MS
#define CFG_SW_KEY_INIT_LLPRESS_THRESH_MS 10000
#endif
#ifndef CFG_SW_KEY_CHECK_INTERVAL_MS
#define CFG_SW_KEY_CHECK_INTERVAL_MS 40
#endif
三、按键响应和处理
第二节的内容有兴趣可以深入研究下,对于实际项目来说,还是需要多关注下按键的响应流程和处理。
1、按键响应流程
当按键初始化完成后,当硬件上按键按键后,驱动层会上报按键的键值和按键事件,具体函数见第二节。上报的按键信息通过消息队列传递至对应的按键执行函数,对应的函数及功能描述如下:
static int key_event_process(uint32_t key_code, uint8_t key_event)
按键消息传递函数,同时增加了按键消息过滤机制,当按键上报太多,就废弃掉最新的按键。
static int app_key_handle_process(APP_MESSAGE_BODY *msg_body)
按键消息接收函数,该函数会通过键值和事件找到对应的执行函数。
void app_key_init(void)
按键初始化,TWS模式和Stereo模式的处理不一致,需要特别注意。该函数非常重要,涉及到按键的执行函数设定,需要特别注意。
下面是按键键值、按键事件和按键执行函数的对应对应关系,用户可以按照需求定义。此处展示的TWS模式的按键配置。
const APP_KEY_HANDLE app_ibrt_ui_test_key_cfg[] =
{
{{APP_KEY_CODE_GOOGLE, APP_KEY_EVENT_FIRST_DOWN}, "google assistant key",app_ibrt_ui_test_voice_assistant_key, NULL},
{{APP_KEY_CODE_GOOGLE, APP_KEY_EVENT_UP}, "google assistant key",app_ibrt_ui_test_voice_assistant_key, NULL},
{{APP_KEY_CODE_GOOGLE, APP_KEY_EVENT_LONGPRESS}, "google assistant key",app_ibrt_ui_test_voice_assistant_key, NULL},
{{APP_KEY_CODE_GOOGLE, APP_KEY_EVENT_CLICK}, "google assistant key",app_ibrt_ui_test_voice_assistant_key, NULL},
{{APP_KEY_CODE_GOOGLE, APP_KEY_EVENT_DOUBLECLICK}, "google assistant key",app_ibrt_ui_test_voice_assistant_key, NULL},
{{APP_KEY_CODE_PWR,APP_KEY_EVENT_DOWN},"app_ibrt_ui_test_key_local",app_ibrt_ui_test_key_local, NULL},
{{APP_KEY_CODE_PWR,APP_KEY_EVENT_CLICK},"bt anc key",app_anc_key, NULL}
{{APP_KEY_CODE_PWR,APP_KEY_EVENT_LONGPRESS},"app_ibrt_ui_test_key",app_ibrt_ui_test_key, NULL},
{{APP_KEY_CODE_PWR,APP_KEY_EVENT_LONGLONGPRESS},"app_ibrt_ui_test_key",app_ibrt_ui_test_key, NULL},
{{APP_KEY_CODE_PWR,APP_KEY_EVENT_DOUBLECLICK},"app_ibrt_ui_test_key",app_ibrt_ui_test_key, NULL},
{{APP_KEY_CODE_PWR,APP_KEY_EVENT_TRIPLECLICK},"app_ibrt_ui_test_key",app_ibrt_ui_test_key, NULL},
{{APP_KEY_CODE_PWR,APP_KEY_EVENT_ULTRACLICK},"app_ibrt_ui_test_key",app_ibrt_ui_test_key, NULL},
{{APP_KEY_CODE_PWR,APP_KEY_EVENT_RAMPAGECLICK},"app_ibrt_ui_test_key",app_ibrt_ui_test_key, NULL},
{{APP_KEY_CODE_PWR,APP_KEY_EVENT_RAMPAGECLICK},"bt i2c key",app_factorymode_i2c_switch, NULL},
{{APP_KEY_CODE_FN1,APP_KEY_EVENT_CLICK},"app_ibrt_ui_test_key",app_ibrt_simulate_charger_plugin_key, NULL},
{{APP_KEY_CODE_FN1,APP_KEY_EVENT_DOUBLECLICK},"app_ibrt_ui_test_key",app_ibrt_simulate_tws_role_switch, NULL},
{{APP_KEY_CODE_FN1,APP_KEY_EVENT_CLICK},"app_ibrt_ui_test_key",app_ibrt_ui_test_key_io_event, NULL},
{{APP_KEY_CODE_FN1,APP_KEY_EVENT_DOUBLECLICK},"app_ibrt_ui_test_key",app_ibrt_ui_test_key_io_event, NULL},
{{APP_KEY_CODE_FN2,APP_KEY_EVENT_CLICK},"app_ibrt_ui_test_key",app_ibrt_ui_test_key_io_event, NULL},
{{APP_KEY_CODE_FN2,APP_KEY_EVENT_DOUBLECLICK},"app_ibrt_ui_test_key",app_ibrt_ui_test_key_io_event, NULL},
{{APP_KEY_CODE_FN3,APP_KEY_EVENT_CLICK},"app_ibrt_ui_test_key",app_ibrt_ui_test_key_io_event, NULL},
{{APP_KEY_CODE_FN3,APP_KEY_EVENT_DOUBLECLICK},"app_ibrt_ui_test_key",app_ibrt_ui_test_key_io_event, NULL},
};
2、按键处理
上面的按键执行函数,例如:
void app_ibrt_ui_test_key_io_event(APP_KEY_STATUS *status, void *param)
void app_ibrt_simulate_charger_plugout_key(APP_KEY_STATUS *status, void *param)
void app_ibrt_simulate_tws_role_switch(APP_KEY_STATUS *status, void *param)
void app_ibrt_simulate_charger_plugout_key(APP_KEY_STATUS *status, void *param)
void app_factorymode_i2c_switch(APP_KEY_STATUS *status, void *param)
void app_ibrt_ui_test_key(APP_KEY_STATUS *status, void *param)
void app_ibrt_ui_test_key_local(APP_KEY_STATUS *status, void *param)
void app_ibrt_ui_test_voice_assistant_key(APP_KEY_STATUS *status, void *param)
此处单使用下面函数举例
void app_ibrt_ui_test_key(APP_KEY_STATUS *status, void *param)
{
TRACE(3,"%s %d,%d",__func__, status->code, status->event);
#ifndef TILE_DATAPATH
ibrt_ctrl_t *p_ibrt_ctrl = app_tws_ibrt_get_bt_ctrl_ctx();
uint8_t shutdown_key = HAL_KEY_EVENT_LONGLONGPRESS;
#endif
#ifndef TILE_DATAPATH
if (IBRT_SLAVE == p_ibrt_ctrl->current_role && status->event != shutdown_key)
{
app_ibrt_if_keyboard_notify(status,param);
}
else
#endif
{
#ifdef IBRT_SEARCH_UI
app_ibrt_search_ui_handle_key(status,param);
#else
app_ibrt_normal_ui_handle_key(status,param);
#endif
}
}
这里需要注意的地方就是需要区分主从耳的按键事件,Stereo方案就不需要这一步,收到按键直接执行即可。由于主耳按键和Stereo方案处理方式是一致的,这里就不多展开了,只需要在对应的键值下面做好需要执行的事件就行,下面的函数是按键响应的入口。
void app_ibrt_search_ui_handle_key(APP_KEY_STATUS *status, void *param)
对应的响应事件则通过对应的函数去实现,可以自由调用,例如双击事件。
void bt_key_handle_func_doubleclick()
{
TRACE(0,"!!!APP_KEY_EVENT_DOUBLECLICK\n");
if((app_bt_device.hfchan_callSetup[BT_DEVICE_ID_1] == BTIF_HF_CALL_SETUP_NONE)&&(app_bt_device.hfchan_call[BT_DEVICE_ID_1] == BTIF_HF_CALL_NONE)){
hfp_handle_key(HFP_KEY_REDIAL_LAST_CALL);
}
if(app_bt_device.hf_audio_state[BT_DEVICE_ID_1] == BTIF_HF_AUDIO_CON){
if(app_bt_device.hf_mute_flag == 0){
hfp_handle_key(HFP_KEY_MUTE);
app_bt_device.hf_mute_flag = 1;
}else{
hfp_handle_key(HFP_KEY_CLEAR_MUTE);
app_bt_device.hf_mute_flag = 0;
}
}
}
此处根据当前的场景,双击事件有三个操作,无业务状态下,双击自动重拨,通话中,则自动在Mute麦克风和Open麦克风之间切换。这里需要注意的就是判断条件需要考虑周全,否则对应的按键起不到对应的效果。
对于从耳来说,执行相对复杂一点,不过也只是多了一步双耳之间按键同步的消息,不过也需要注意,若该操作仅从耳执行,那操作和主耳一致,判断按键事件,执行对应的操作即可,不需要同步,例如单耳关机。其中从耳执行流程如下:
int app_ibrt_if_keyboard_notify(APP_KEY_STATUS *status, void *param)
{
if (app_tws_ibrt_slave_ibrt_link_connected())
{
tws_ctrl_send_cmd(APP_TWS_CMD_KEYBOARD_REQUEST, (uint8_t *)status,sizeof(APP_KEY_STATUS));
}
return 0;
}
其中下面的函数就是双耳同步消息的函数,此处不展开,暂时也不需要深入了解,知道函数咋用就行了。进阶后可以研究一下完整的流程和原理。
int tws_ctrl_send_cmd(uint32_t cmd_code, uint8_t *p_buff, uint16_t length)
从耳发出按键消息后,主耳会同步收到对应的消息,随后的流程和主耳执行的按键流程一致。对应的入口为:
void app_ibrt_keyboard_request_handler(uint16_t rsp_seq, uint8_t *p_buff, uint16_t length)
{
if (app_tws_ibrt_mobile_link_connected())
{
#ifdef IBRT_SEARCH_UI
app_ibrt_search_ui_handle_key((APP_KEY_STATUS *)p_buff, NULL);
#else
app_ibrt_normal_ui_handle_key((APP_KEY_STATUS *)p_buff, NULL);
#endif
#ifdef __AI_VOICE__
app_ibrt_ui_test_voice_assistant_key((APP_KEY_STATUS *)p_buff, NULL);
#endif
}
}
其中对应的按键响应入口和主耳操作一样。如下:
void app_ibrt_search_ui_handle_key(APP_KEY_STATUS *status, void *param)
四、补充
此处补充一下平时遇到的一些按键模块中的一些小问题。
1、按键响应函数中的事件处理不宜添加延时等阻塞型函数和锁函数,否则有可能导致系统死机和死锁;
2、有些用户需要修改双击和三击的间隔,而且还不一致,这个可以在驱动层做修改,不过需要注意其中的按键释放,否则可能会导致按键持续触发;
3、按键的GPIO映射时,对应的高低电平和触发需要注意,保持和原理图一致。GPIO的上下拉需要设定好。
{HAL_KEY_CODE_FN2,{HAL_IOMUX_PIN_P0_1, HAL_IOMUX_FUNC_AS_GPIO, HAL_IOMUX_PIN_VOLTAGE_VIO, HAL_IOMUX_PIN_PULLUP_ENABLE}},
4、给按键设置别名时,参考按键的功能。其中组合键可以将对应的按键或在一起。
#define BTAPP_FUNC_KEY APP_KEY_CODE_PWR
#define BTAPP_VOLUME_DOWN_KEY APP_KEY_CODE_FN1
#define BTAPP_VOLUME_UP_KEY APP_KEY_CODE_FN2
#define BTAPP_PLAY_KEY APP_KEY_CODE_FN3
#define BTAPP_SLEEP_KEY APP_KEY_CODE_FN4
#define BTAPP_FUNC_UP_DOWN_KEY (BTAPP_FUNC_KEY|BTAPP_VOLUME_DOWN_KEY|BTAPP_VOLUME_UP_KEY)
按键模块总体比较简单,对于其他类型的按键,例如触摸按键、ADC按键,以及其他类似按键(如佩戴、陀螺仪等外设,可以通过模拟成按键来操作)的处理,后续想到新的再补充。