本帖最后由 sunsili 于 2024-4-4 17:50 编辑
中科蓝讯 SDK 开发——SDK按键功能简析
在使用中科蓝讯芯片开发时候,一些 UI 上的功能通常都会使用到按键操作,当然自己在软件中添加按键功能并不困难,但是中科蓝讯的 SDK 中通常都已经做好了常用的按键功能检测,足以满足大部分操作需求,并且中科蓝讯不同芯片的 SDK 按键的操作判断都是类似的,直接使用 SDK 中做好的按键检测可以很大程度上帮助开发者节省开发时间,下面为大家介绍如何使用 SDK 中做好的按键检测实现相应功能。
一、按键的类型
在 SDK 中按键可以大致分为四个类型: PWRKEY:POWER KEY 平时使用最多的按键,按键是接到芯片具有 WK 功能的引脚上,可以用做软件开关机按键,例如 BT892X 系列的 PB5,
有些芯片只有单独的一个引脚作为 POWER KEY; AD KEY: AD KEY 通过在按键按下时 IO 口 ADC 采样到的电压值来判断当前按下的按键,在 IO 口不够用,但又需要多个按键时推荐使用,PWRKEY 也可以作为 AD KEY; IO KEY:即一个 IO 对应一个按键。 TKEY: Touch KEY 这个是用的是芯片内部的 Touch IC,具体的配置可以看我另一篇介绍 Touch KEY 的文章《中科蓝讯 SDK 开发——BT892XA2 芯片内置 Touch 使用》。
需要注意的是,使用外部的 Touch 芯片并不属于这个类型。
二、SDK 中的按键检测
下面以 BT892X 的按键检测为例简单讲解 SDK 中的按键功能是如何实现的,其他芯片的也大同小异。 首先在 config.h 中打开需要使用的按键宏开关,对应在 setting 中的按键配置页面也需要打开(本文章所粘贴代码均为 SDK 源码,由于部分代码内容与所述内容无关或源码过长,粘贴的源码有省略,用“......”表示,完整内容可以查看 SDK)。
- /*****************************************************************************
- * Module : User按键配置 (可以同时选择多组按键)
- *****************************************************************************/
- #define USER_ADKEY 1 //ADKEY的使用, 0为不使用
- #define USER_ADKEY2 0 //ADKEY2的使用,0为不使用
- #define USER_PWRKEY 1 //PWRKEY的使用,0为不使用
- #define USER_IOKEY 1 //IOKEY的使用, 0为不使用
复制代码
按键处理的相关函数都会在 bsp_key.c 中,按键的初始化在 key_init(void),在初始化中会对打开的按键功能进行初始化,例如配置 AD KEY 的 ADC 的通道,配置 IO KEY 对应的 IO 的初始化等。
- void key_init(void)
- {
- u16 adc_ch = 0;
- key_var_init();
- #if USER_IOKEY
- io_key_init();
- #endif
- #if USER_ADKEY
- if (xcfg_cb.user_adkey_en) {
- adc_ch |= BIT(ADKEY_CH);
- #if ADKEY_PU10K_EN
- adcch_io_pu10k_enable(ADKEY_CH); //开内部10K上拉
- #endif // ADKEY_PU10K_EN
- }
- #endif // USER_ADKEY
- ......
- #if USER_PWRKEY
- if (sys_cb.wko_pwrkey_en) {
- #if POWER_KEY_USE_HIGHLEVEL
- adcch_io_pd10k_enable(ADCCH_WKO);
- #else
- adcch_io_pu10k_enable(ADCCH_WKO);
- #endif
- adc_ch |= BIT(ADCCH_WKO);
- pwr_usage_id = pwrkey_table[0].usage_id;
- if (xcfg_cb.pwrkey_config_en) {
- pwr_usage_id = key_config_table[xcfg_cb.pwrkey_num0];
- }
- #if POWER_KEY_USE_HIGHLEVEL
- RTCCON13 |= BIT(0) | BIT(8) | BIT(12); //wk pin0 wakeup, input, pulldown10k enable
- #else
- RTCCON13 |= BIT(0) | BIT(4) | BIT(12); //wk pin0 wakeup, input, pullup10k enable
- #endif
- } else
- #endif // USER_PWRKEY
- {
- GPIOBDE &= ~BIT(5);
- GPIOBDIR |= BIT(5);
- GPIOBPU &= ~BIT(5);
- GPIOBPD &= ~BIT(5);
- RTCCON13 &= ~(BIT(0) | BIT(4) | BIT(12));
- }
- ......
- bsp_tkey_init();
- }
复制代码
按键的检测 bsp_key_scan() 函数是放在 5ms 的中断处理函数中连续检测。
- AT(.com_text.timer)
- void usr_tmr5ms_thread(void)
- {
- tmr5ms_cnt++;
- //5ms timer process
- dac_fade_process();
- #if !USER_KEY_KNOB2_EN
- bsp_key_scan();
- #endif
- ......
- }
复制代码
拿到 BT892XA2 的开发板,可以看到每一侧的耳机板上对应有三个按键,这三个按键在是接在 PWRKEY(PB5) 上,并且在 PWRKEY 中使用了 AD KEY 的功能,因此一个 PWRKEY IO 可以连接三个按键,那么就以最右边这个按键为例讲解按键检测的大致流程。
bsp_key_scan() 中的按键检测主要可以看 key_val = bsp_key_scan_do()、key = bsp_key_process(key_val)、 msg_enqueue(key) 三个部分。
- u8 bsp_key_scan(void)
- {
- u8 key_val;
- u16 key = NO_KEY;
- key_val = bsp_key_scan_do();
- ......
- key = bsp_key_process(key_val);
- ......
- msg_enqueue(key);
-
- return key_val;
- }
复制代码
第一部分,PWRKEY 按键按下,bsp_key_scan_do() 中, get_adc_val() 获取对应 ADC 通道的 ADC 值,由于不同的按键串联的电阻不同,按下按键后,PWRKEY IO 采集到的 ADC 值也会不同,最终在保存在 adc_cb.wko_val 中,并在 get_pwrkey() 中做具体按键的检测。
- u8 bsp_key_scan_do(void)
- {
- u8 key_val = NO_KEY;
- if (!get_adc_val()) {
- return NO_KEY;
- }
- #if USER_TKEY
- key_val = bsp_tkey_scan();
- #endif
- #if USER_ADKEY
- if (key_val == NO_KEY) {
- key_val = get_adkey(adc_cb.key_val, xcfg_cb.user_adkey_en);
- }
- #endif // USER_ADKEY
- #if USER_ADKEY2
- if (key_val == NO_KEY) {
- key_val = get_adkey2();
- }
- #endif // USER_ADKEY2
- #if USER_PWRKEY
- if ((key_val == NO_KEY) && (!PWRKEY_2_HW_PWRON)) {
- key_val = get_pwrkey();
- }
- #endif // USER_PWRKEY
- ......
- return key_val;
- }
复制代码
get_pwrkey() 通过判断 adc_cb.wko_val 的值对应到定义好的 KEY 表中,得到对应的按键,调试过程中如果出现有的按键没反应或者按键对应不上的情况,那么就打印出 adc_cb.key_val 的值,根据实际的值去修改 pwrkey_table[],或排查硬件设计问题。
- static u8 get_pwrkey(void)
- {
- u8 num = 0;
- u8 *ptr;
- // //配置工具是否使能了PWRKEY?
- if ((!xcfg_cb.user_pwrkey_en) && (!PWRKEY_2_HW_PWRON)) {
- return NO_KEY;
- }
- // printf("adc_cb.wko_val == %d",adc_cb.wko_val);
- while ((u8)adc_cb.wko_val > pwrkey_table[num].adc_val) {
- num++;
- }
- //工具配置了PWRKEY的按键定义?
- ptr = get_pwrkey_configure(num);
- if (ptr != NULL) {
- #if POWER_KEY_USE_HIGHLEVEL
- if(num > 5){
- #else
- if (num > 4) {
- #endif
- return NO_KEY;
- }
- return key_config_table[*(ptr+num)];
- }
- return pwrkey_table[num].usage_id;
- }
复制代码
例如按下开发板最右边的按键,此时 adc_cb.wko_val 中保存的 ADC 值为 0x8E ,对应 pwrkey_table[],0x70< adc_cb.wko_val <0xAF,那么可以得出按下的按键是 KEY_VOL_DOWN。
- const adkey_tbl_t pwrkey_table[6] = {
- #if POWER_KEY_USE_HIGHLEVEL
- {0x0A, NO_KEY}, //P/P POWER 0
- {0x34, NO_KEY}, //PREV/VOL- 1.5K
- {0x70, NO_KEY}, //NEXT/VOL+ 3.9K
- {0xAF, NO_KEY}, //VOL- 15K
- {0xE1, KEY_PLAY_PWR_USER_DEF}, //VOL+ 33K
- {0xFF, KEY_PLAY_PWR_USER_DEF},
- #else
- {0x0A, KEY_PLAY_PWR_USER_DEF}, //P/P POWER 0
- {0x34, KEY_PREV_VOL_DOWN}, //PREV/VOL- 1.5K
- {0x70, KEY_NEXT_VOL_UP}, //NEXT/VOL+ 3.9K
- {0xAF, KEY_VOL_DOWN}, //VOL- 15K
- {0xE1, KEY_VOL_UP}, //VOL+ 33K
- {0xFF, NO_KEY},
- #endif
- };
复制代码
第二部分,获取到对应的按键后,回到 bsp_key_scan() ,在 bsp_key_process() 中,会返回对应的按键操作,如果需要检测多击功能需要打开多击检测的宏 USER_MULTI_PRESS_EN。
- u16 bsp_key_process(u16 key_val)
- {
- u16 key_return = NO_KEY;
- ......
- key_return = key_process(key_val);
- //双击处理
- #if USER_MULTI_PRESS_EN
- //配置工具是否使能了按键2/3/4/5击功能?
- if (xcfg_cb.user_key_multi_press_en) {
- key_return = key_multi_press_process(key_return);
- }
- #endif
- return key_return;
- #endif
- }
复制代码
这里检测按键原厂在底层中已经做好单击到五击和长按的按键检测,对应的返回值可以看到 bsp_key.h 中,以单击按键 KEY_VOL_DOWN 为例,整个单击过程会收到对应的按键消息宏为 K_VOL_DOWN(短按按下)、KU_VOL_DOWN(短按抬起),其他的按键操作也类似。这里也可以给大家一个小提示,开发板中只去做到五击,如果需要更多击的检测实际上,去计数 KU_VOL_DOWN(短按抬起)的次数是可以简单实现多击功能的。
- #define K_VOL_DOWN (KEY_VOL_DOWN | KEY_SHORT)
- #define KU_VOL_DOWN (KEY_VOL_DOWN | KEY_SHORT_UP)
- #define KL_VOL_DOWN (KEY_VOL_DOWN | KEY_LONG)
- #define KLU_VOL_DOWN (KEY_VOL_DOWN | KEY_LONG_UP)
- #define KH_VOL_DOWN (KEY_VOL_DOWN | KEY_HOLD)
- #define KD_VOL_DOWN (KEY_VOL_DOWN | KEY_DOUBLE)
- #define KTH_VOL_DOWN (KEY_VOL_DOWN | KEY_THREE)
复制代码
最后 bsp_key_scan() 中的第三部分,msg_enqueue(key),则是将按键消息发到消息队列中处理,这里按键按下的操作 KU_VOL_DOWN,在 SDK 中可以看到,最终 KEY_VOL_DOWN 按键单击的消息处理会在 func_message(u16 msg) 中执行音量减。
- void func_message(u16 msg)
- {
- switch (msg) {
- ......
- case KU_VOL_DOWN:
- case KL_VOL_DOWN:
- case KH_VOL_DOWN:
- case KL_VOL_UP_DOWN:
- case KH_VOL_UP_DOWN:
- if(bt_is_support_vol_ctrl() && bsp_bt_hid_vol_change(HID_KEY_VOL_DOWN)){
- if (!sys_cb.incall_flag) {
- #if WARNING_MIN_VOLUME
- if (sys_cb.vol == 0) {
- if (func_cb.mp3_res_play) {
- func_cb.mp3_res_play(RES_BUF_MAX_VOL_MP3, RES_LEN_MAX_VOL_MP3);
- }
- }
- #endif // WARNING_MIN_VOLUME
- }
- }else{
- if (sys_cb.incall_flag) {
- bsp_bt_call_volume_msg(KU_VOL_DOWN);
- } else {
- bsp_set_volume(bsp_volume_dec(sys_cb.vol));
- bsp_bt_vol_change();
- printf("current volume: %d\n", sys_cb.vol);
- #if WARNING_MIN_VOLUME
- if (sys_cb.vol == 0) {
- if (func_cb.mp3_res_play) {
- func_cb.mp3_res_play(RES_BUF_MAX_VOL_MP3, RES_LEN_MAX_VOL_MP3);
- }
- }
- #endif // WARNING_MIN_VOLUME
- if (func_cb.set_vol_callback) {
- func_cb.set_vol_callback(0);
- }
- }
- }
- break;
- ........
- }
- }
复制代码
以上三个部分,大致就是按键的检测过程,都是 SDK 中已经做好的检测功能,对于开发者来说,仅需要找到对应的按键操作消息宏,并在对应的 message() 函数中添加想要实现的功能即可。
三、Setting 配置按键功能
除了上面列出来的在软件上去添加需要的按键操作外,对于一些简单的按键操作,开发者可以在 setting 中去配置实现,可以看到 func_bt_message() 中的按键处理下,会执行 user_def_key_msg(xcfg_cb.user_def_kfive_sel)。 - void func_bt_message(u16 msg)
- {
- int klu_flag = 0;
- switch (msg) {
- ......
- case KL_PLAY_USER_DEF:
- ......
- user_def_key_msg(xcfg_cb.user_def_kl_sel);
- ......
- break;
- //SIRI, NEXT, PREV在长按抬键的时候响应,避免关机前切歌或呼SIRI了
- case KLU_PLAY_PWR_USER_DEF:
- if (f_bt.user_kl_flag) {
- user_def_key_msg(xcfg_cb.user_def_kl_sel);
- f_bt.user_kl_flag = 0;
- }
- break;
- ......
- ///三击按键处理
- case KTH_PLAY_USER_DEF:
- case KTH_PLAY_PWR_USER_DEF:
- user_def_key_msg(xcfg_cb.user_def_kt_sel);
- break;
- ///四击按键处理
- case KFO_PLAY_USER_DEF:
- case KFO_PLAY_PWR_USER_DEF:
- user_def_key_msg(xcfg_cb.user_def_kfour_sel);
- break;
- ///五击按键处理
- case KFI_PLAY_USER_DEF:
- case KFI_PLAY_PWR_USER_DEF:
- if (xcfg_cb.user_def_kfive_sel) {
- user_def_key_msg(xcfg_cb.user_def_kfive_sel);
- }
- break;
- ......
- }
复制代码
user_def_key_msg() 会根据 setting 中配置的内容去完成相应的功能;
- ///检查USER_DEF按键消息处理
- bool user_def_key_msg(u8 func_sel)
- {
- u16 msg = NO_MSG;
- if (!user_def_func_is_ready(func_sel)) {
- return false;
- }
- if (func_sel == UDK_REDIALING) {
- bt_call_redial_last_number(); //回拨电话
- if (func_cb.mp3_res_play) {
- func_cb.mp3_res_play(RES_BUF_REDIALING_MP3, RES_LEN_REDIALING_MP3);
- }
- } else if (func_sel == UDK_SIRI) { //SIRI
- bt_hfp_siri_switch();
- } else if (func_sel == UDK_NR) { //NR
- bt_ctl_nr_sta_change();
- } else if (func_sel == UDK_PREV) { //PREV
- if(xcfg_cb.user_def_lr_en) {
- msg = func_bt_tws_get_channel()? KU_PREV : KU_NEXT;
- } else {
- msg = KU_PREV;
- }
- user_def_track_msg(msg);
- } else if (func_sel == UDK_NEXT) { //NEXT
- if(xcfg_cb.user_def_lr_en) {
- msg = func_bt_tws_get_channel()? KU_NEXT : KU_PREV;
- } else {
- msg = KU_NEXT;
- }
- user_def_track_msg(msg);
- } else if (func_sel == UDK_MODE) { //MODE
- func_message(KU_MODE);
- } else if (func_sel == UDK_PHOTO) {
- return bsp_bt_hid_photo(HID_KEY_VOL_UP); //拍照
- } else if (func_sel == UDK_HOME) {
- return bt_hid_consumer(HID_KEY_IOS_HOME); //IOS Home按键功能
- } else if (func_sel == UDK_LANG) {
- func_bt_switch_voice_lang(); //中英文切换
- } else if (func_sel == UDK_PLAY_PAUSE) {
- bt_music_play_pause();
- } else if (func_sel == UDK_DUT) { //CBT 测试模式
- if(func_cb.sta != FUNC_BT_DUT){
- func_cb.sta = FUNC_BT_DUT;
- sys_cb.discon_reason = 0;
- }
- } else if (func_sel == UDK_LOW_LATENCY) {
- bool low_latency = bt_is_low_latency();
- if (low_latency) {
- bsp_tws_res_music_play(TWS_RES_MUSIC_MODE);
- } else {
- bsp_tws_res_music_play(TWS_RES_GAME_MODE);
- }
- } else if (func_sel == UDK_TWS_CLEAR){
- #if BT_TWS_BONDING_CLEAR_EN
- bt_tws_clr_bondlink_info();
- #endif
- } else { //VOL+, VOL-
- func_message(get_user_def_vol_msg(func_sel));
- }
- return true;
- }
复制代码
例如在 setting 中,可以直接对 PWRKEY 配置对应的按键的操作,例如配置双击回拨,三击切歌,等等。同样的其他类型的按键也是可以在 setting 中直接配置功能,如果这里的配置不能满足需求,则在代码中对应的按键消息处理中添加代码实现功能即可,此时不使用 setting 配置按键,可以将 setting 中按键配置关闭,或直接在代码中注释掉 user_def_key_msg(xcfg_cb.user_def_kt_sel)。
[1] 蓝皮书 Downloader 可视化配置 — 中科蓝讯
[2] 蓝皮书 TWS 开发板使用说明 — 中科蓝讯
|