谷动谷力

标题: 中科蓝讯 SDK 开发——SDK按键功能简析 [打印本页]

作者: sunsili    时间: 2022-12-10 22:40
标题: 中科蓝讯 SDK 开发——SDK按键功能简析
本帖最后由 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)。
  1. /*****************************************************************************
  2. * Module    : User按键配置 (可以同时选择多组按键)
  3. *****************************************************************************/
  4. #define USER_ADKEY                      1           //ADKEY的使用, 0为不使用
  5. #define USER_ADKEY2                     0           //ADKEY2的使用,0为不使用
  6. #define USER_PWRKEY                     1           //PWRKEY的使用,0为不使用
  7. #define USER_IOKEY                      1           //IOKEY的使用, 0为不使用
复制代码

    按键处理的相关函数都会在 bsp_key.c 中,按键的初始化在 key_init(void),在初始化中会对打开的按键功能进行初始化,例如配置 AD KEY 的 ADC 的通道,配置 IO KEY 对应的 IO 的初始化等。
  1. void key_init(void)
  2. {
  3.     u16 adc_ch = 0;

  4.     key_var_init();

  5. #if USER_IOKEY
  6.     io_key_init();
  7. #endif

  8. #if USER_ADKEY
  9.     if (xcfg_cb.user_adkey_en) {
  10.         adc_ch |= BIT(ADKEY_CH);
  11.     #if ADKEY_PU10K_EN
  12.         adcch_io_pu10k_enable(ADKEY_CH);        //开内部10K上拉
  13.     #endif // ADKEY_PU10K_EN
  14.     }
  15. #endif // USER_ADKEY

  16. ......

  17. #if USER_PWRKEY
  18.     if (sys_cb.wko_pwrkey_en) {
  19.                 #if POWER_KEY_USE_HIGHLEVEL
  20.                 adcch_io_pd10k_enable(ADCCH_WKO);
  21.                 #else
  22.         adcch_io_pu10k_enable(ADCCH_WKO);
  23.                 #endif
  24.         adc_ch |= BIT(ADCCH_WKO);
  25.         pwr_usage_id = pwrkey_table[0].usage_id;
  26.         if (xcfg_cb.pwrkey_config_en) {
  27.             pwr_usage_id = key_config_table[xcfg_cb.pwrkey_num0];
  28.         }
  29.                 #if POWER_KEY_USE_HIGHLEVEL
  30.         RTCCON13 |= BIT(0) | BIT(8) | BIT(12);  //wk pin0 wakeup, input, pulldown10k enable
  31.                 #else
  32.         RTCCON13 |= BIT(0) | BIT(4) | BIT(12);  //wk pin0 wakeup, input, pullup10k enable
  33.                 #endif
  34.     } else
  35. #endif // USER_PWRKEY
  36.     {
  37.         GPIOBDE &= ~BIT(5);
  38.         GPIOBDIR |= BIT(5);
  39.         GPIOBPU &= ~BIT(5);
  40.         GPIOBPD &= ~BIT(5);
  41.         RTCCON13 &= ~(BIT(0) | BIT(4) | BIT(12));
  42.     }

  43. ......

  44.     bsp_tkey_init();
  45. }
复制代码

    按键的检测 bsp_key_scan() 函数是放在 5ms 的中断处理函数中连续检测。
  1. AT(.com_text.timer)
  2. void usr_tmr5ms_thread(void)
  3. {
  4.     tmr5ms_cnt++;
  5.     //5ms timer process
  6.     dac_fade_process();
  7. #if !USER_KEY_KNOB2_EN
  8.     bsp_key_scan();
  9. #endif
  10. ......
  11. }
复制代码

    拿到 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) 三个部分。
  1. u8 bsp_key_scan(void)
  2. {
  3.     u8 key_val;
  4.     u16 key = NO_KEY;

  5.     key_val = bsp_key_scan_do();

  6.     ......

  7.     key = bsp_key_process(key_val);

  8.     ......

  9.     msg_enqueue(key);
  10.    
  11.     return key_val;
  12. }
复制代码

    第一部分,PWRKEY 按键按下,bsp_key_scan_do() 中, get_adc_val() 获取对应 ADC 通道的 ADC 值,由于不同的按键串联的电阻不同,按下按键后,PWRKEY IO 采集到的 ADC 值也会不同,最终在保存在 adc_cb.wko_val 中,并在 get_pwrkey() 中做具体按键的检测。
  1. u8 bsp_key_scan_do(void)
  2. {
  3.     u8 key_val = NO_KEY;

  4.     if (!get_adc_val()) {
  5.         return NO_KEY;
  6.     }

  7. #if USER_TKEY
  8.     key_val = bsp_tkey_scan();
  9. #endif

  10. #if USER_ADKEY
  11.     if (key_val == NO_KEY) {
  12.         key_val = get_adkey(adc_cb.key_val, xcfg_cb.user_adkey_en);
  13.     }
  14. #endif // USER_ADKEY

  15. #if USER_ADKEY2
  16.     if (key_val == NO_KEY) {
  17.         key_val = get_adkey2();
  18.     }
  19. #endif // USER_ADKEY2

  20. #if USER_PWRKEY
  21.     if ((key_val == NO_KEY) && (!PWRKEY_2_HW_PWRON)) {
  22.         key_val = get_pwrkey();
  23.     }
  24. #endif // USER_PWRKEY

  25. ......

  26.     return key_val;
  27. }
复制代码

    get_pwrkey() 通过判断 adc_cb.wko_val 的值对应到定义好的 KEY 表中,得到对应的按键,调试过程中如果出现有的按键没反应或者按键对应不上的情况,那么就打印出 adc_cb.key_val 的值,根据实际的值去修改 pwrkey_table[],或排查硬件设计问题。
  1. static u8 get_pwrkey(void)
  2. {
  3.     u8 num = 0;
  4.     u8 *ptr;

  5. //    //配置工具是否使能了PWRKEY?
  6.     if ((!xcfg_cb.user_pwrkey_en) && (!PWRKEY_2_HW_PWRON)) {
  7.         return NO_KEY;
  8.     }
  9. //     printf("adc_cb.wko_val == %d",adc_cb.wko_val);
  10.     while ((u8)adc_cb.wko_val > pwrkey_table[num].adc_val) {
  11.         num++;
  12.     }
  13.     //工具配置了PWRKEY的按键定义?
  14.     ptr = get_pwrkey_configure(num);
  15.     if (ptr != NULL) {
  16.                 #if POWER_KEY_USE_HIGHLEVEL
  17.                 if(num > 5){
  18.                 #else
  19.         if (num > 4) {
  20.                 #endif
  21.             return NO_KEY;
  22.         }
  23.         return key_config_table[*(ptr+num)];
  24.     }
  25.     return pwrkey_table[num].usage_id;
  26. }
复制代码

    例如按下开发板最右边的按键,此时 adc_cb.wko_val 中保存的 ADC 值为 0x8E ,对应 pwrkey_table[],0x70< adc_cb.wko_val <0xAF,那么可以得出按下的按键是 KEY_VOL_DOWN。
  1. const adkey_tbl_t pwrkey_table[6] = {
  2. #if POWER_KEY_USE_HIGHLEVEL
  3.                 {0x0A, NO_KEY},          //P/P POWER         0
  4.                 {0x34, NO_KEY},                  //PREV/VOL-         1.5K
  5.                 {0x70, NO_KEY},                    //NEXT/VOL+           3.9K
  6.                 {0xAF, NO_KEY},                            //VOL-                   15K
  7.                 {0xE1, KEY_PLAY_PWR_USER_DEF},                                   //VOL+                   33K
  8.                 {0xFF, KEY_PLAY_PWR_USER_DEF},
  9. #else
  10.                 {0x0A, KEY_PLAY_PWR_USER_DEF},                //P/P POWER         0
  11.                 {0x34, KEY_PREV_VOL_DOWN},                        //PREV/VOL-         1.5K
  12.                 {0x70, KEY_NEXT_VOL_UP},                        //NEXT/VOL+         3.9K
  13.                 {0xAF, KEY_VOL_DOWN},                                 //VOL-                  15K
  14.                 {0xE1, KEY_VOL_UP},                                 //VOL+                        33K
  15.                 {0xFF, NO_KEY},
  16. #endif
  17. };
复制代码

    第二部分,获取到对应的按键后,回到 bsp_key_scan() ,在 bsp_key_process() 中,会返回对应的按键操作,如果需要检测多击功能需要打开多击检测的宏 USER_MULTI_PRESS_EN。
  1. u16 bsp_key_process(u16 key_val)
  2. {
  3.     u16 key_return = NO_KEY;
  4. ......
  5.         key_return = key_process(key_val);

  6. //双击处理
  7. #if USER_MULTI_PRESS_EN
  8. //配置工具是否使能了按键2/3/4/5击功能?
  9. if (xcfg_cb.user_key_multi_press_en) {
  10. key_return = key_multi_press_process(key_return);
  11. }
  12. #endif
  13. return key_return;
  14. #endif
  15. }
复制代码

    这里检测按键原厂在底层中已经做好单击到五击和长按的按键检测,对应的返回值可以看到 bsp_key.h 中,以单击按键 KEY_VOL_DOWN 为例,整个单击过程会收到对应的按键消息宏为 K_VOL_DOWN(短按按下)、KU_VOL_DOWN(短按抬起),其他的按键操作也类似。这里也可以给大家一个小提示,开发板中只去做到五击,如果需要更多击的检测实际上,去计数 KU_VOL_DOWN(短按抬起)的次数是可以简单实现多击功能的。
  1. #define K_VOL_DOWN              (KEY_VOL_DOWN | KEY_SHORT)
  2. #define KU_VOL_DOWN             (KEY_VOL_DOWN | KEY_SHORT_UP)
  3. #define KL_VOL_DOWN             (KEY_VOL_DOWN | KEY_LONG)
  4. #define KLU_VOL_DOWN            (KEY_VOL_DOWN | KEY_LONG_UP)
  5. #define KH_VOL_DOWN             (KEY_VOL_DOWN | KEY_HOLD)
  6. #define KD_VOL_DOWN             (KEY_VOL_DOWN | KEY_DOUBLE)
  7. #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) 中执行音量减。
  1. void func_message(u16 msg)
  2. {
  3.     switch (msg) {

  4. ......

  5.         case KU_VOL_DOWN:
  6.         case KL_VOL_DOWN:
  7.         case KH_VOL_DOWN:
  8.         case KL_VOL_UP_DOWN:
  9.         case KH_VOL_UP_DOWN:
  10.             if(bt_is_support_vol_ctrl() && bsp_bt_hid_vol_change(HID_KEY_VOL_DOWN)){
  11.                 if (!sys_cb.incall_flag) {
  12.             #if WARNING_MIN_VOLUME
  13.                     if (sys_cb.vol == 0) {
  14.                         if (func_cb.mp3_res_play) {
  15.                             func_cb.mp3_res_play(RES_BUF_MAX_VOL_MP3, RES_LEN_MAX_VOL_MP3);
  16.                         }
  17.                     }
  18.             #endif // WARNING_MIN_VOLUME
  19.                 }
  20.             }else{
  21.                 if (sys_cb.incall_flag) {
  22.                     bsp_bt_call_volume_msg(KU_VOL_DOWN);
  23.                 } else {
  24.                     bsp_set_volume(bsp_volume_dec(sys_cb.vol));
  25.                     bsp_bt_vol_change();
  26.                     printf("current volume: %d\n", sys_cb.vol);
  27.             #if WARNING_MIN_VOLUME
  28.                     if (sys_cb.vol == 0) {
  29.                         if (func_cb.mp3_res_play) {
  30.                             func_cb.mp3_res_play(RES_BUF_MAX_VOL_MP3, RES_LEN_MAX_VOL_MP3);
  31.                         }
  32.                     }
  33.             #endif // WARNING_MIN_VOLUME
  34.                     if (func_cb.set_vol_callback) {
  35.                         func_cb.set_vol_callback(0);
  36.                     }
  37.                 }
  38.             }
  39.             break;
  40. ........
  41. }
  42. }
复制代码

    以上三个部分,大致就是按键的检测过程,都是 SDK 中已经做好的检测功能,对于开发者来说,仅需要找到对应的按键操作消息宏,并在对应的 message() 函数中添加想要实现的功能即可。

三、Setting 配置按键功能

    除了上面列出来的在软件上去添加需要的按键操作外,对于一些简单的按键操作,开发者可以在 setting 中去配置实现,可以看到 func_bt_message() 中的按键处理下,会执行 user_def_key_msg(xcfg_cb.user_def_kfive_sel)。
  1. void func_bt_message(u16 msg)
  2. {
  3.     int klu_flag = 0;

  4.     switch (msg) {
  5. ......
  6.     case KL_PLAY_USER_DEF:
  7. ......
  8.          user_def_key_msg(xcfg_cb.user_def_kl_sel);
  9. ......
  10.         break;

  11.         //SIRI, NEXT, PREV在长按抬键的时候响应,避免关机前切歌或呼SIRI了
  12.     case KLU_PLAY_PWR_USER_DEF:
  13.         if (f_bt.user_kl_flag) {
  14.             user_def_key_msg(xcfg_cb.user_def_kl_sel);
  15.             f_bt.user_kl_flag = 0;
  16.         }
  17.         break;

  18. ......

  19.     ///三击按键处理
  20.     case KTH_PLAY_USER_DEF:
  21.     case KTH_PLAY_PWR_USER_DEF:
  22.         user_def_key_msg(xcfg_cb.user_def_kt_sel);
  23.         break;

  24.     ///四击按键处理
  25.     case KFO_PLAY_USER_DEF:
  26.     case KFO_PLAY_PWR_USER_DEF:
  27.         user_def_key_msg(xcfg_cb.user_def_kfour_sel);
  28.         break;

  29.     ///五击按键处理
  30.     case KFI_PLAY_USER_DEF:
  31.     case KFI_PLAY_PWR_USER_DEF:
  32.         if (xcfg_cb.user_def_kfive_sel) {
  33.             user_def_key_msg(xcfg_cb.user_def_kfive_sel);
  34.         }
  35.         break;

  36. ......

  37. }
复制代码


      user_def_key_msg() 会根据 setting 中配置的内容去完成相应的功能;
  1. ///检查USER_DEF按键消息处理
  2. bool user_def_key_msg(u8 func_sel)
  3. {
  4.     u16 msg = NO_MSG;

  5.     if (!user_def_func_is_ready(func_sel)) {
  6.         return false;
  7.     }

  8.     if (func_sel == UDK_REDIALING) {
  9.         bt_call_redial_last_number();                   //回拨电话
  10.         if (func_cb.mp3_res_play) {
  11.             func_cb.mp3_res_play(RES_BUF_REDIALING_MP3, RES_LEN_REDIALING_MP3);
  12.         }
  13.     } else if (func_sel == UDK_SIRI) {                  //SIRI
  14.         bt_hfp_siri_switch();
  15.     } else if (func_sel == UDK_NR) {                    //NR
  16.         bt_ctl_nr_sta_change();
  17.     } else if (func_sel == UDK_PREV) {                  //PREV
  18.         if(xcfg_cb.user_def_lr_en) {
  19.             msg = func_bt_tws_get_channel()? KU_PREV : KU_NEXT;
  20.         } else {
  21.             msg = KU_PREV;
  22.         }
  23.         user_def_track_msg(msg);
  24.     } else if (func_sel == UDK_NEXT) {                  //NEXT
  25.         if(xcfg_cb.user_def_lr_en) {
  26.             msg = func_bt_tws_get_channel()? KU_NEXT : KU_PREV;
  27.         } else {
  28.             msg = KU_NEXT;
  29.         }
  30.         user_def_track_msg(msg);
  31.     } else if (func_sel == UDK_MODE) {                  //MODE
  32.         func_message(KU_MODE);
  33.     } else if (func_sel == UDK_PHOTO) {
  34.         return bsp_bt_hid_photo(HID_KEY_VOL_UP);        //拍照
  35.     } else if (func_sel == UDK_HOME) {
  36.         return bt_hid_consumer(HID_KEY_IOS_HOME);       //IOS Home按键功能
  37.     } else if (func_sel == UDK_LANG) {
  38.         func_bt_switch_voice_lang();                    //中英文切换
  39.     } else if (func_sel == UDK_PLAY_PAUSE) {
  40.         bt_music_play_pause();
  41.     } else if (func_sel == UDK_DUT) {                  //CBT 测试模式
  42.         if(func_cb.sta != FUNC_BT_DUT){
  43.             func_cb.sta = FUNC_BT_DUT;
  44.             sys_cb.discon_reason = 0;
  45.         }
  46.     } else if (func_sel == UDK_LOW_LATENCY) {
  47.         bool low_latency = bt_is_low_latency();
  48.         if (low_latency) {
  49.             bsp_tws_res_music_play(TWS_RES_MUSIC_MODE);
  50.         } else {
  51.             bsp_tws_res_music_play(TWS_RES_GAME_MODE);
  52.         }
  53.     } else if (func_sel == UDK_TWS_CLEAR){
  54. #if BT_TWS_BONDING_CLEAR_EN
  55.         bt_tws_clr_bondlink_info();
  56. #endif
  57.     } else {                                            //VOL+, VOL-
  58.         func_message(get_user_def_vol_msg(func_sel));
  59.     }
  60.     return true;
  61. }
复制代码

    例如在 setting 中,可以直接对 PWRKEY 配置对应的按键的操作,例如配置双击回拨,三击切歌,等等。同样的其他类型的按键也是可以在 setting 中直接配置功能,如果这里的配置不能满足需求,则在代码中对应的按键消息处理中添加代码实现功能即可,此时不使用 setting 配置按键,可以将 setting 中按键配置关闭,或直接在代码中注释掉 user_def_key_msg(xcfg_cb.user_def_kt_sel)。



    以上就是要分享的全部内容,内容有错误或者遗漏欢迎大家指出,有其他问题也可以在评论区提出,共同学习讨论。

延伸阅读
共同关键字:中科蓝讯 SDK
中科蓝讯 SDK 开发环境安装及 Downloader 配置
中科蓝讯 SDK 开发——工程浅析
中科蓝讯 SDK 开发——耳机充电配置
中科蓝讯 SDK TWS 组队和蓝牙配对过程分析
中科蓝讯 SDK 开发——TWS 左右声道分配

参考文献:
[1] 蓝皮书 Downloader 可视化配置              — 中科蓝讯
[2] 蓝皮书 TWS 开发板使用说明                    — 中科蓝讯






欢迎光临 谷动谷力 (http://bbs.sunsili.com/) Powered by Discuz! X3.2